Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
commonMain.com.soywiz.korim.format.jpg.JPEGDecoder.kt Maven / Gradle / Ivy
/* Spotify Web API, Kotlin Wrapper; MIT License, 2017-2022; Original author: Adam Ratzman */
package com.soywiz.korim.format.jpg
import com.soywiz.kmem.UByteArrayInt
import com.soywiz.kmem.asByteArray
import com.soywiz.korim.bitmap.Bitmap32
import com.soywiz.korim.color.RGBA
import com.soywiz.korim.color.decodeToBitmap32
import com.soywiz.korio.lang.invalidOp
import kotlin.math.ceil
// https://github.com/eugeneware/jpeg-js/blob/652bfced3ead53808285b1b5fa9c0b589d00bbf0/lib/decoder.js
/*
Copyright 2011 notmasteryet
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// - The JPEG specification can be found in the ITU CCITT Recommendation T.81
// (www.w3.org/Graphics/JPEG/itu-t81.pdf)
// - The JFIF specification can be found in the JPEG File Interchange Format
// (www.w3.org/Graphics/JPEG/jfif3.pdf)
// - The Adobe Application-Specific JPEG markers in the Supporting the DCT Filters
// in PostScript Level 2, Technical Note #5116
// (partners.adobe.com/public/developer/en/ps/sdk/5116.DCT_Filter.pdf)
public class JPEGDecoder {
private var dctZigZag = intArrayOf(
0,
1, 8,
16, 9, 2,
3, 10, 17, 24,
32, 25, 18, 11, 4,
5, 12, 19, 26, 33, 40,
48, 41, 34, 27, 20, 13, 6,
7, 14, 21, 28, 35, 42, 49, 56,
57, 50, 43, 36, 29, 22, 15,
23, 30, 37, 44, 51, 58,
59, 52, 45, 38, 31,
39, 46, 53, 60,
61, 54, 47,
55, 62,
63
)
private var dctCos1 = 4017 // cos(pi/16)
private var dctSin1 = 799 // sin(pi/16)
private var dctCos3 = 3406 // cos(3*pi/16)
private var dctSin3 = 2276 // sin(3*pi/16)
private var dctCos6 = 1567 // cos(6*pi/16)
private var dctSin6 = 3784 // sin(6*pi/16)
private var dctSqrt2 = 5793 // sqrt(2)
private var dctSqrt1d2 = 2896 // sqrt(2) / 2
@Suppress("UNUSED_PARAMETER")
public class Jfif(
versionMajor: Int,
versionMinor: Int,
densityUnits: Int,
xDensity: Int,
yDensity: Int,
thumbWidth: Int,
thumbHeight: Int,
thumbData: UByteArrayInt
)
@Suppress("unused")
public class Adobe(
public val version: Int,
public val flags0: Int,
public val flags1: Int,
public val transformCode: Boolean
)
private var width: Int = 0
private var height: Int = 0
private var jfif: Jfif? = null
private var adobe: Adobe? = null
private var components = arrayListOf()
private var colorTransform: Boolean? = null
private fun mceil(v: Float): Int = ceil(v).toInt()
public data class HuffmanNode(var children: ArrayList = arrayListOf(), var index: Int = 0) {
public fun setChildAt(index: Int, value: Any) {
while (children.size <= index) children.add(Unit) // Todo use other thing?
children[index] = value
}
}
private fun buildHuffmanTable(codeLengths: UByteArrayInt, values: UByteArrayInt): List {
var k = 0
val code = arrayListOf()
var length = 16
while (length > 0 && (0 == codeLengths[length - 1])) length--
code.add(HuffmanNode())
var p: HuffmanNode = code[0]
var q: HuffmanNode
for (i in 0 until length) {
for (j in 0 until codeLengths[i]) {
p = code.removeAt(code.size - 1)
p.setChildAt(p.index, values[k])
while (p.index > 0) {
p = code.removeAt(code.size - 1)
}
p.index++
code.add(p)
while (code.size <= i) {
q = HuffmanNode()
code.add(q)
p.setChildAt(p.index, q.children)
p = q
}
k++
}
if (i + 1 < length) {
// p here points to last code
q = HuffmanNode()
code.add(q)
p.setChildAt(p.index, q.children)
p = q
}
}
return code[0].children
}
private fun decodeScan(
data: UByteArrayInt,
offset: Int,
frame: Frame,
components: List,
resetInterval: Int,
spectralStart: Int,
spectralEnd: Int,
successivePrev: Int,
successive: Int
): Int {
@Suppress("NAME_SHADOWING")
var resetInterval = resetInterval
@Suppress("NAME_SHADOWING")
var offset = offset
val mcusPerLine = frame.mcusPerLine
val progressive = frame.progressive
// var precision = frame.precision
// var samplesPerLine = frame.samplesPerLine
// var scanLines = frame.scanLines
// var maxH = frame.maxH
// var maxV = frame.maxV
val startOffset = offset
var bitsData = 0
var bitsCount = 0
fun readBit(): Int {
if (bitsCount > 0) {
bitsCount--
return (bitsData shr bitsCount) and 1
}
bitsData = data[offset++]
if (bitsData == 0xFF) {
val nextByte = data[offset++]
if (nextByte != 0) {
invalidOp("unexpected marker: " + ((bitsData shl 8) or nextByte).toString(16))
}
// unstuff 0
}
bitsCount = 7
return bitsData ushr 7
}
fun decodeHuffman(tree: List): Int {
var node: List = tree
while (true) {
val bit = readBit()
val res = node[bit]
@Suppress("UNCHECKED_CAST")
when (res) {
is Int -> return res
is List<*> -> node = res as List
else -> invalidOp("invalid huffman sequence")
}
}
}
fun receive(length: Int): Int {
var len = length
var n = 0
while (len > 0) {
val bit = readBit()
n = (n shl 1) or bit
len--
}
return n
}
fun receiveAndExtend(length: Int): Int {
val n = receive(length)
if (n >= (1 shl (length - 1))) return n
return n + (-1 shl length) + 1
}
fun decodeBaseline(component: FrameComponent, zz: IntArray) {
val t = decodeHuffman(component.huffmanTableDC)
val diff = if (t == 0) 0 else receiveAndExtend(t)
component.pred += diff
zz[0] = component.pred
var k = 1
while (k < 64) {
val rs = decodeHuffman(component.huffmanTableAC)
val s = rs and 15
val r = rs shr 4
if (s == 0) {
if (r < 15) break
k += 16
continue
}
k += r
val z = dctZigZag[k]
zz[z] = receiveAndExtend(s)
k++
}
}
fun decodeDCFirst(component: FrameComponent, zz: IntArray) {
val t = decodeHuffman(component.huffmanTableDC)
val diff = if (t == 0) 0 else (receiveAndExtend(t) shl successive)
component.pred += diff
zz[0] = component.pred
}
fun decodeDCSuccessive(@Suppress("UNUSED_PARAMETER") component: FrameComponent, zz: IntArray) {
zz[0] = zz[0] or (readBit() shl successive)
}
var eobrun = 0
fun decodeACFirst(component: FrameComponent, zz: IntArray) {
if (eobrun > 0) {
eobrun--
return
}
var k = spectralStart
@Suppress("UnnecessaryVariable")
val e = spectralEnd
while (k <= e) {
val rs = decodeHuffman(component.huffmanTableAC)
val s = rs and 15
val r = rs shr 4
if (s == 0) {
if (r < 15) {
eobrun = receive(r) + (1 shl r) - 1
break
}
k += 16
continue
}
k += r
val z = dctZigZag[k]
zz[z] = receiveAndExtend(s) * (1 shl successive)
k++
}
}
var successiveACState = 0
var successiveACNextValue = 0
fun decodeACSuccessive(component: FrameComponent, zz: IntArray) {
var k = spectralStart
@Suppress("UnnecessaryVariable")
val e = spectralEnd
var r = 0
loop@ while (k <= e) {
val z = dctZigZag[k]
val direction = if (zz[z] < 0) -1 else 1
when (successiveACState) {
0 -> { // initial state
val rs = decodeHuffman(component.huffmanTableAC)
val s = rs and 15
r = rs shr 4
if (s == 0) {
if (r < 15) {
eobrun = receive(r) + (1 shl r)
successiveACState = 4
} else {
r = 16
successiveACState = 1
}
} else {
if (s != 1) invalidOp("invalid ACn encoding")
successiveACNextValue = receiveAndExtend(s)
successiveACState = if (r != 0) 2 else 3
}
continue@loop
}
1, 2 -> { // skipping r zero items
if (zz[z] != 0) {
zz[z] += (readBit() shl successive) * direction
} else {
r--
if (r == 0) successiveACState = if (successiveACState == 2) 3 else 0
}
}
3 -> { // set value for a zero item
if (zz[z] != 0) {
zz[z] += (readBit() shl successive) * direction
} else {
zz[z] = successiveACNextValue shl successive
successiveACState = 0
}
}
4 -> { // eob
if (zz[z] != 0) {
zz[z] += (readBit() shl successive) * direction
}
}
}
k++
}
if (successiveACState == 4) {
eobrun--
if (eobrun == 0) {
successiveACState = 0
}
}
}
fun decodeMcu(
component: FrameComponent,
decode: (FrameComponent, IntArray) -> Unit,
mcu: Int,
row: Int,
col: Int
) {
val mcuRow = (mcu / mcusPerLine) or 0
val mcuCol = mcu % mcusPerLine
val blockRow = mcuRow * component.v + row
val blockCol = mcuCol * component.h + col
decode(component, component.blocks[blockRow][blockCol])
}
fun decodeBlock(component: FrameComponent, decode: (FrameComponent, IntArray) -> Unit, mcu: Int) {
val blockRow = (mcu / component.blocksPerLine) or 0
val blockCol = mcu % component.blocksPerLine
decode(component, component.blocks[blockRow][blockCol])
}
val componentsLength = components.size
var component: FrameComponent
val decodeFn = if (progressive) {
if (spectralStart == 0) {
if (successivePrev == 0) ::decodeDCFirst else ::decodeDCSuccessive
} else if (successivePrev == 0) ::decodeACFirst else ::decodeACSuccessive
} else {
::decodeBaseline
}
var mcu = 0
val mcuExpected = if (componentsLength == 1) {
components[0].blocksPerLine * components[0].blocksPerColumn
} else {
mcusPerLine * frame.mcusPerColumn
}
if (resetInterval == 0) {
resetInterval = mcuExpected
}
var h: Int
var v: Int
while (mcu < mcuExpected) {
// reset interval stuff
for (i in 0 until componentsLength) components[i].pred = 0
eobrun = 0
if (componentsLength == 1) {
component = components[0]
for (n in 0 until resetInterval) {
decodeBlock(component, decodeFn, mcu)
mcu++
}
} else {
for (n in 0 until resetInterval) {
for (i in 0 until componentsLength) {
component = components[i]
h = component.h
v = component.v
for (j in 0 until v) {
for (k in 0 until h) {
decodeMcu(component, decodeFn, mcu, j, k)
}
}
}
mcu++
// If we've reached our expected MCU's, stop decoding
if (mcu == mcuExpected) break
}
}
// find marker
bitsCount = 0
val marker = (data[offset] shl 8) or data[offset + 1]
if (marker < 0xFF00) {
invalidOp("marker was not found")
}
if (marker in 0xFFD0..0xFFD7) { // RSTx
offset += 2
} else {
break
}
}
return offset - startOffset
}
private fun buildComponentData(
@Suppress("UNUSED_PARAMETER") frame: Frame,
component: FrameComponent
): List {
val lines = arrayListOf()
val blocksPerLine = component.blocksPerLine
val blocksPerColumn = component.blocksPerColumn
val samplesPerLine = blocksPerLine shl 3
val rr = IntArray(64)
val r = UByteArrayInt(64)
// A port of poppler's IDCT method which in turn is taken from:
// Christoph Loeffler, Adriaan Ligtenberg, George S. Moschytz,
// "Practical Fast 1-D DCT Algorithms with 11 Multiplications",
// IEEE Intl. Conf. on Acoustics, Speech and Signal Processing, 1989,
// 988-991.
fun quantizeAndInverse(zz: IntArray, dataOut: UByteArrayInt, dataIn: IntArray) {
val qt = component.quantizationTable
@Suppress("UnnecessaryVariable")
val p = dataIn
// dequant
for (i in 0 until 64) p[i] = zz[i] * qt[i]
// inverse DCT on rows
for (i in 0 until 8) {
val row = 8 * i
// check for all-zero AC coefficients
if (p[1 + row] == 0 && p[2 + row] == 0 && p[3 + row] == 0 &&
p[4 + row] == 0 && p[5 + row] == 0 && p[6 + row] == 0 &&
p[7 + row] == 0
) {
val t = (dctSqrt2 * p[0 + row] + 512) shr 10
p[0 + row] = t
p[1 + row] = t
p[2 + row] = t
p[3 + row] = t
p[4 + row] = t
p[5 + row] = t
p[6 + row] = t
p[7 + row] = t
continue
}
// stage 4
var v0 = (dctSqrt2 * p[0 + row] + 128) shr 8
var v1 = (dctSqrt2 * p[4 + row] + 128) shr 8
var v2 = p[2 + row]
var v3 = p[6 + row]
var v4 = (dctSqrt1d2 * (p[1 + row] - p[7 + row]) + 128) shr 8
var v7 = (dctSqrt1d2 * (p[1 + row] + p[7 + row]) + 128) shr 8
var v5 = p[3 + row] shl 4
var v6 = p[5 + row] shl 4
// stage 3
var t = (v0 - v1 + 1) shr 1
v0 = (v0 + v1 + 1) shr 1
v1 = t
t = (v2 * dctSin6 + v3 * dctCos6 + 128) shr 8
v2 = (v2 * dctCos6 - v3 * dctSin6 + 128) shr 8
v3 = t
t = (v4 - v6 + 1) shr 1
v4 = (v4 + v6 + 1) shr 1
v6 = t
t = (v7 + v5 + 1) shr 1
v5 = (v7 - v5 + 1) shr 1
v7 = t
// stage 2
t = (v0 - v3 + 1) shr 1
v0 = (v0 + v3 + 1) shr 1
v3 = t
t = (v1 - v2 + 1) shr 1
v1 = (v1 + v2 + 1) shr 1
v2 = t
t = (v4 * dctSin3 + v7 * dctCos3 + 2048) shr 12
v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) shr 12
v7 = t
t = (v5 * dctSin1 + v6 * dctCos1 + 2048) shr 12
v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) shr 12
v6 = t
// stage 1
p[0 + row] = v0 + v7
p[7 + row] = v0 - v7
p[1 + row] = v1 + v6
p[6 + row] = v1 - v6
p[2 + row] = v2 + v5
p[5 + row] = v2 - v5
p[3 + row] = v3 + v4
p[4 + row] = v3 - v4
}
// inverse DCT on columns
for (col in 0 until 8) {
// check for all-zero AC coefficients
if (p[1 * 8 + col] == 0 && p[2 * 8 + col] == 0 && p[3 * 8 + col] == 0 &&
p[4 * 8 + col] == 0 && p[5 * 8 + col] == 0 && p[6 * 8 + col] == 0 &&
p[7 * 8 + col] == 0
) {
val t = (dctSqrt2 * dataIn[col + 0] + 8192) shr 14
p[0 * 8 + col] = t
p[1 * 8 + col] = t
p[2 * 8 + col] = t
p[3 * 8 + col] = t
p[4 * 8 + col] = t
p[5 * 8 + col] = t
p[6 * 8 + col] = t
p[7 * 8 + col] = t
continue
}
// stage 4
var v0 = (dctSqrt2 * p[0 * 8 + col] + 2048) shr 12
var v1 = (dctSqrt2 * p[4 * 8 + col] + 2048) shr 12
var v2 = p[2 * 8 + col]
var v3 = p[6 * 8 + col]
var v4 = (dctSqrt1d2 * (p[1 * 8 + col] - p[7 * 8 + col]) + 2048) shr 12
var v7 = (dctSqrt1d2 * (p[1 * 8 + col] + p[7 * 8 + col]) + 2048) shr 12
var v5 = p[3 * 8 + col]
var v6 = p[5 * 8 + col]
// stage 3
var t = (v0 - v1 + 1) shr 1
v0 = (v0 + v1 + 1) shr 1
v1 = t
t = (v2 * dctSin6 + v3 * dctCos6 + 2048) shr 12
v2 = (v2 * dctCos6 - v3 * dctSin6 + 2048) shr 12
v3 = t
t = (v4 - v6 + 1) shr 1
v4 = (v4 + v6 + 1) shr 1
v6 = t
t = (v7 + v5 + 1) shr 1
v5 = (v7 - v5 + 1) shr 1
v7 = t
// stage 2
t = (v0 - v3 + 1) shr 1
v0 = (v0 + v3 + 1) shr 1
v3 = t
t = (v1 - v2 + 1) shr 1
v1 = (v1 + v2 + 1) shr 1
v2 = t
t = (v4 * dctSin3 + v7 * dctCos3 + 2048) shr 12
v4 = (v4 * dctCos3 - v7 * dctSin3 + 2048) shr 12
v7 = t
t = (v5 * dctSin1 + v6 * dctCos1 + 2048) shr 12
v5 = (v5 * dctCos1 - v6 * dctSin1 + 2048) shr 12
v6 = t
// stage 1
p[0 * 8 + col] = v0 + v7
p[7 * 8 + col] = v0 - v7
p[1 * 8 + col] = v1 + v6
p[6 * 8 + col] = v1 - v6
p[2 * 8 + col] = v2 + v5
p[5 * 8 + col] = v2 - v5
p[3 * 8 + col] = v3 + v4
p[4 * 8 + col] = v3 - v4
}
// convert to 8-bit integers
for (i in 0 until 64) {
val sample = 128 + ((p[i] + 8) shr 4)
dataOut[i] = if (sample < 0) 0 else if (sample > 0xFF) 0xFF else sample
}
}
for (blockRow in 0 until blocksPerColumn) {
val scanLine = blockRow shl 3
for (i in 0 until 8)
lines.add(UByteArrayInt(samplesPerLine))
for (blockCol in 0 until blocksPerLine) {
quantizeAndInverse(component.blocks[blockRow][blockCol], r, rr)
var offset = 0
val sample = blockCol shl 3
for (j in 0 until 8) {
val line = lines[scanLine + j]
for (i in 0 until 8)
line[sample + i] = r[offset++]
}
}
}
return lines
}
private fun clampTo8bit(a: Int): Int = if (a < 0) 0 else if (a > 255) 255 else a
// private fun clampTo8bit(a: Float): Float = if (a < 0f) 0f else if (a > 255f) 255f else a
private fun UByteArrayInt.subarray(from: Int, to: Int) = UByteArrayInt(this.asByteArray().copyOfRange(from, to))
public class FrameComponent(
public val h: Int,
public val v: Int,
public var quantizationIdx: Int
) {
public var huffmanTableDC: List = emptyList()
public var huffmanTableAC: List = emptyList()
public var quantizationTable: IntArray = IntArray(0)
public var pred: Int = 0
public var blocksPerLine: Int = 0
public var blocksPerColumn: Int = 0
public var blocks: List> = emptyList()
}
@Suppress("unused")
public class Frame(
public var extended: Boolean,
public var progressive: Boolean,
public var precision: Int,
public var scanLines: Int,
public var samplesPerLine: Int,
public var components: ArrayList,
public var componentsOrder: ArrayList,
public var maxH: Int = 0,
public var maxV: Int = 0
) {
public var mcusPerLine: Int = 0
public var mcusPerColumn: Int = 0
}
public fun parse(data: UByteArrayInt) {
var offset = 0
// var length = data.size
fun readUint16(): Int {
val value = (data[offset] shl 8) or data[offset + 1]
offset += 2
return value
}
fun readDataBlock(): UByteArrayInt {
val len = readUint16()
val array = UByteArrayInt(data.asByteArray().copyOfRange(offset, offset + len - 2))
offset += array.size
return array
}
fun prepareComponents(frame: Frame) {
var maxH = 0
var maxV = 0
for (component in frame.components) {
if (maxH < component.h) maxH = component.h
if (maxV < component.v) maxV = component.v
}
val mcusPerLine = mceil(frame.samplesPerLine.toFloat() / 8f / maxH.toFloat())
val mcusPerColumn = mceil(frame.scanLines.toFloat() / 8f / maxV.toFloat())
for (component in frame.components) {
val blocksPerLine =
mceil(mceil(frame.samplesPerLine.toFloat() / 8f) * component.h.toFloat() / maxH.toFloat())
val blocksPerColumn =
mceil(mceil(frame.scanLines.toFloat() / 8f) * component.v.toFloat() / maxV.toFloat())
val blocksPerLineForMcu = mcusPerLine * component.h
val blocksPerColumnForMcu = mcusPerColumn * component.v
val blocks = arrayListOf>()
for (i in 0 until blocksPerColumnForMcu) {
val row = arrayListOf()
for (j in 0 until blocksPerLineForMcu)
row.add(IntArray(64))
blocks.add(row)
}
component.blocksPerLine = blocksPerLine
component.blocksPerColumn = blocksPerColumn
component.blocks = blocks
}
frame.maxH = maxH
frame.maxV = maxV
frame.mcusPerLine = mcusPerLine
frame.mcusPerColumn = mcusPerColumn
}
var jfif: Jfif? = null
var adobe: Adobe? = null
// var pixels = null
lateinit var frame: Frame
var resetInterval = 0
val quantizationTables = ArrayList((0 until 16).map { IntArray(0) })
val frames = arrayListOf ()
val huffmanTablesAC = ArrayList((0 until 16).map { emptyList() })
val huffmanTablesDC = ArrayList((0 until 16).map { emptyList() })
var fileMarker = readUint16()
if (fileMarker != 0xFFD8) { // SOI (Start of Image)
invalidOp("SOI not found")
}
fileMarker = readUint16()
while (fileMarker != 0xFFD9) { // EOI (End of image)
when (fileMarker) {
0xFF00 -> Unit
// APP0-15 (Application Specific), COM (Comment)
0xFFE0, 0xFFE1, 0xFFE2, 0xFFE3, 0xFFE4, 0xFFE5, 0xFFE6, 0xFFE7, 0xFFE8,
0xFFE9, 0xFFEA, 0xFFEB, 0xFFEC, 0xFFED, 0xFFEE, 0xFFEF, 0xFFFE -> {
val appData = readDataBlock()
if (fileMarker == 0xFFE0) {
if (appData[0] == 0x4A && appData[1] == 0x46 && appData[2] == 0x49 &&
appData[3] == 0x46 && appData[4] == 0
) { // 'JFIF\x00'
jfif = Jfif(
versionMajor = appData[5],
versionMinor = appData[6],
densityUnits = appData[7],
xDensity = (appData[8] shl 8) or appData[9],
yDensity = (appData[10] shl 8) or appData[11],
thumbWidth = appData[12],
thumbHeight = appData[13],
thumbData = appData.subarray(14, 14 + 3 * appData[12] * appData[13])
)
}
}
// TODO APP1 - Exif
if (fileMarker == 0xFFEE) {
if (appData[0] == 0x41 && appData[1] == 0x64 && appData[2] == 0x6F &&
appData[3] == 0x62 && appData[4] == 0x65 && appData[5] == 0
) { // 'Adobe\x00'
adobe = Adobe(
version = appData[6],
flags0 = (appData[7] shl 8) or appData[8],
flags1 = (appData[9] shl 8) or appData[10],
transformCode = appData[11] != 0
)
}
}
}
0xFFDB -> { // DQT (Define Quantization Tables)
val quantizationTablesLength = readUint16()
val quantizationTablesEnd = quantizationTablesLength + offset - 2
while (offset < quantizationTablesEnd) {
val quantizationTableSpec = data[offset++]
val tableData = IntArray(64)
when (quantizationTableSpec shr 4) {
0 -> for (j in 0 until 64) tableData[dctZigZag[j]] = data[offset++] // 8 bit values
1 -> for (j in 0 until 64) tableData[dctZigZag[j]] = readUint16() // 16 bit
else -> invalidOp("DQT: invalid table spec")
}
quantizationTables[quantizationTableSpec and 15] = tableData
}
}
0xFFC0, 0xFFC1, 0xFFC2 -> {
// SOF0 (Start of Frame, Baseline DCT), SOF1 (Start of Frame, Extended DCT), SOF2 (Start of Frame, Progressive DCT)
readUint16() // skip data length
frame = Frame(
extended = (fileMarker == 0xFFC1),
progressive = (fileMarker == 0xFFC2),
precision = data[offset++],
scanLines = readUint16(),
samplesPerLine = readUint16(),
components = ArrayList(),
componentsOrder = arrayListOf()
)
val componentsCount = data[offset++]
var componentId: Int
// var maxH = 0
// var maxV = 0
for (i in 0 until componentsCount) {
componentId = data[offset]
val h = data[offset + 1] shr 4
val v = data[offset + 1] and 15
val qId = data[offset + 2]
frame.componentsOrder.add(componentId)
while (frame.components.size <= componentId) frame.components.add(FrameComponent(0, 0, 0))
frame.components[componentId] = FrameComponent(h = h, v = v, quantizationIdx = qId)
offset += 3
}
prepareComponents(frame)
frames.add(frame)
}
0xFFC4 -> { // DHT (Define Huffman Tables)
val huffmanLength = readUint16()
var i = 2
while (i < huffmanLength) {
val huffmanTableSpec = data[offset++]
val codeLengths = UByteArrayInt(16)
var codeLengthSum = 0
for (j in 0 until 16) {
codeLengths[j] = data[offset]
codeLengthSum += codeLengths[j]
offset++
}
val huffmanValues = UByteArrayInt(codeLengthSum)
for (j in 0 until codeLengthSum) {
huffmanValues[j] = data[offset]
offset++
}
i += 17 + codeLengthSum
val table = if ((huffmanTableSpec shr 4) == 0) huffmanTablesDC else huffmanTablesAC
table[huffmanTableSpec and 15] = buildHuffmanTable(codeLengths, huffmanValues)
}
}
0xFFDD -> { // DRI (Define Restart Interval)
readUint16() // skip data length
resetInterval = readUint16()
}
0xFFDA -> { // SOS (Start of Scan)
@Suppress("UNUSED_VARIABLE")
var scanLength = readUint16()
val selectorsCount = data[offset++]
val components = arrayListOf()
var component: FrameComponent
for (i in 0 until selectorsCount) {
component = frame.components[data[offset++]]
val tableSpec = data[offset++]
component.huffmanTableDC = huffmanTablesDC[tableSpec shr 4]
component.huffmanTableAC = huffmanTablesAC[tableSpec and 15]
components.add(component)
}
val spectralStart = data[offset++]
val spectralEnd = data[offset++]
val successiveApproximation = data[offset++]
val processed = decodeScan(
data, offset,
frame, components, resetInterval,
spectralStart, spectralEnd,
successiveApproximation shr 4,
successiveApproximation and 15
)
offset += processed
}
else -> {
if (data[offset - 3] == 0xFF &&
data[offset - 2] >= 0xC0 && data[offset - 2] <= 0xFE
) {
// could be incorrect encoding -- last 0xFF byte of the previous
// block was eaten by the encoder
offset -= 3
} else {
invalidOp("unknown JPEG marker " + fileMarker.toString(16))
}
}
}
fileMarker = readUint16()
}
if (frames.size != 1) invalidOp("only single frame JPEGs supported")
// set each frame's components quantization table
for (i in 0 until frames.size) {
val cp = frames[i].components
for (c in cp) {
c.quantizationTable = quantizationTables[c.quantizationIdx]
c.quantizationIdx = -1
}
}
this.width = frame.samplesPerLine
this.height = frame.scanLines
this.jfif = jfif
this.adobe = adobe
this.components = arrayListOf()
for (i in 0 until frame.componentsOrder.size) {
val component = frame.components[frame.componentsOrder[i]]
this.components.add(
Component(
lines = buildComponentData(frame, component),
scaleX = component.h.toFloat() / frame.maxH.toFloat(),
scaleY = component.v.toFloat() / frame.maxV.toFloat()
)
)
}
}
public class Component(public val lines: List, public val scaleX: Float, public val scaleY: Float)
private fun getData(width: Int, height: Int): UByteArrayInt {
val scaleX = this.width / width
val scaleY = this.height / height
var offset = 0
val dataLength = width * height * this.components.size
val data = UByteArrayInt(dataLength)
when (this.components.size) {
1 -> {
val component1 = this.components[0]
for (y in 0 until height) {
val component1Line = component1.lines[((y * component1.scaleY * scaleY).toInt())]
for (x in 0 until width) {
data[offset++] = component1Line[((x * component1.scaleX * scaleX).toInt())]
}
}
}
2 -> {
// PDF might compress two component data in custom colorspace
val component1 = this.components[0]
val component2 = this.components[1]
for (y in 0 until height) {
val component1Line = component1.lines[((y * component1.scaleY * scaleY).toInt())]
val component2Line = component2.lines[((y * component2.scaleY * scaleY).toInt())]
for (x in 0 until width) {
data[offset++] = component1Line[((x * component1.scaleX * scaleX).toInt())]
data[offset++] = component2Line[((x * component2.scaleX * scaleX).toInt())]
}
}
}
3 -> {
// The adobe transform marker overrides any previous setting
val colorTransform = when {
this.adobe?.transformCode == true -> true
else -> this.colorTransform ?: true
}
val component1 = this.components[0]
val component2 = this.components[1]
val component3 = this.components[2]
for (y in 0 until height) {
val component1Line = component1.lines[((y * component1.scaleY * scaleY).toInt())]
val component2Line = component2.lines[((y * component2.scaleY * scaleY).toInt())]
val component3Line = component3.lines[((y * component3.scaleY * scaleY).toInt())]
if (!colorTransform) {
for (x in 0 until width) {
data[offset++] = component1Line[((x * component1.scaleX * scaleX).toInt())]
data[offset++] = component2Line[((x * component2.scaleX * scaleX).toInt())]
data[offset++] = component3Line[((x * component3.scaleX * scaleX).toInt())]
}
} else {
for (x in 0 until width) {
val yy = component1Line[((x * component1.scaleX * scaleX).toInt())]
val cb = component2Line[((x * component2.scaleX * scaleX).toInt())]
val cr = component3Line[((x * component3.scaleX * scaleX).toInt())]
data[offset++] = clampTo8bit((yy + 1.402f * (cr - 128f)).toInt())
data[offset++] =
clampTo8bit((yy - 0.3441363f * (cb - 128f) - 0.71413636f * (cr - 128f)).toInt())
data[offset++] = clampTo8bit((yy + 1.772f * (cb - 128f)).toInt())
}
}
}
}
4 -> {
if (this.adobe == null) invalidOp("Unsupported color mode (4 components)")
// The default transform for four components is false
// The adobe transform marker overrides any previous setting
val colorTransform = when {
this.adobe?.transformCode == true -> true
else -> this.colorTransform ?: false
}
val component1 = this.components[0]
val component2 = this.components[1]
val component3 = this.components[2]
val component4 = this.components[3]
for (y in 0 until height) {
val component1Line = component1.lines[(y * component1.scaleY * scaleY).toInt()]
val component2Line = component2.lines[(y * component2.scaleY * scaleY).toInt()]
val component3Line = component3.lines[(y * component3.scaleY * scaleY).toInt()]
val component4Line = component4.lines[(y * component4.scaleY * scaleY).toInt()]
for (x in 0 until width) {
val c: Int
val m: Int
val ye: Int
val k: Int
if (!colorTransform) {
c = component1Line[(x * component1.scaleX * scaleX).toInt()]
m = component2Line[(x * component2.scaleX * scaleX).toInt()]
ye = component3Line[(x * component3.scaleX * scaleX).toInt()]
k = component4Line[(x * component4.scaleX * scaleX).toInt()]
} else {
val yy = component1Line[(x * component1.scaleX * scaleX).toInt()]
val cb = component2Line[(x * component2.scaleX * scaleX).toInt()]
val cr = component3Line[(x * component3.scaleX * scaleX).toInt()]
k = component4Line[(x * component4.scaleX * scaleX).toInt()]
c = 255 - clampTo8bit((yy + 1.402 * (cr - 128)).toInt())
m = 255 - clampTo8bit((yy - 0.3441363 * (cb - 128) - 0.71413636 * (cr - 128)).toInt())
ye = 255 - clampTo8bit((yy + 1.772 * (cb - 128)).toInt())
}
data[offset++] = 255 - c
data[offset++] = 255 - m
data[offset++] = 255 - ye
data[offset++] = 255 - k
}
}
}
else -> invalidOp("Unsupported color mode")
}
return data
}
public fun copyToImageData(imageData: ImageData) {
val width = imageData.width
val height = imageData.height
val imageDataArray = imageData.data
val data = this.getData(width, height)
var i = 0
var j = 0
when (this.components.size) {
1 -> {
for (n in 0 until width * height) {
val yy = data[i++]
imageDataArray[j++] = yy
imageDataArray[j++] = yy
imageDataArray[j++] = yy
imageDataArray[j++] = 255
}
}
3 -> {
for (n in 0 until width * height) {
imageDataArray[j++] = data[i++]
imageDataArray[j++] = data[i++]
imageDataArray[j++] = data[i++]
imageDataArray[j++] = 255
}
}
4 -> {
for (n in 0 until width * height) {
val c = data[i++]
val m = data[i++]
val y = data[i++]
val k = data[i++]
imageDataArray[j++] = 255 - clampTo8bit(c * (1 - k / 255) + k)
imageDataArray[j++] = 255 - clampTo8bit(m * (1 - k / 255) + k)
imageDataArray[j++] = 255 - clampTo8bit(y * (1 - k / 255) + k)
imageDataArray[j++] = 255
}
}
else -> invalidOp("Unsupported color mode")
}
}
public data class ImageInfo(val width: Int, val height: Int)
public class ImageData(public val width: Int, public val height: Int, public val data: UByteArrayInt)
public companion object {
public fun decodeInfo(jpegData: ByteArray): ImageInfo {
val arr = UByteArrayInt(jpegData)
val decoder = JPEGDecoder()
decoder.parse(arr)
return ImageInfo(decoder.width, decoder.height)
}
public fun decode(jpegData: ByteArray): Bitmap32 {
val data = decodeToData(jpegData)
return RGBA.decodeToBitmap32(data.width, data.height, data.data.asByteArray())
}
private fun decodeToData(jpegData: ByteArray): ImageData {
val arr = UByteArrayInt(jpegData)
val decoder = JPEGDecoder()
decoder.parse(arr)
val image = ImageData(
width = decoder.width,
height = decoder.height,
data = UByteArrayInt(decoder.width * decoder.height * 4)
)
decoder.copyToImageData(image)
return image
}
}
}