From 060ec0aa2ed0c45211c74caf83200af0d712b1a1 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 30 Dec 2014 15:06:32 +0400 Subject: [PATCH 1/2] Fix warnings --- dlib/image/io/jpeg.d | 1705 +++++++++++++++++++++--------------------- 1 file changed, 852 insertions(+), 853 deletions(-) diff --git a/dlib/image/io/jpeg.d b/dlib/image/io/jpeg.d index ab5720ce..697a34fe 100644 --- a/dlib/image/io/jpeg.d +++ b/dlib/image/io/jpeg.d @@ -24,856 +24,855 @@ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -module dlib.image.io.jpeg; - -private -{ - import std.stdio; - import std.algorithm; - import std.math; - import std.string; - - import dlib.core.stream; - import dlib.filesystem.local; - import dlib.image.color; - import dlib.image.image; - - import dlib.image.io.bitio; - import dlib.image.io.huffman; - import dlib.image.io.idct; -} - -/* - * Simple JPEG decoder - * - * Limitations: - * - Doesn't support progressive JPEG - * - Supports only 4:2:0 subsampling - * - Doesn't perform chroma interpolation - * - Doesn't read EXIF metadata - */ - -// Uncomment this to see debug messages -//version = JPEGDebug; - -T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) -if (is(T == ubyte)) -{ - ubyte b; - istrm.readBytes(&b, 1); - return b; -} - -T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) -if (is(T == ushort)) -{ - union U16 - { - ubyte[2] asBytes; - ushort asUshort; - }; - U16 u16; - istrm.readBytes(u16.asBytes.ptr, 2); - version(LittleEndian) - { - if (endian == Endian.Big) - return u16.asUshort.swapEndian16; - else - return u16.asUshort; - } - else - { - if (endian == Endian.Little) - return u16.asUshort.swapEndian16; - else - return u16.asUshort; - } -} - -char[size] readChars(size_t size) (InputStream istrm) -{ - char[size] chars; - istrm.readBytes(chars.ptr, size); - return chars; -} - -//TODO: make this struct -class JPEGImage -{ - struct JFIF - { - ubyte versionMajor; - ubyte versionMinor; - ubyte units; - ushort xdensity; - ushort ydensity; - ubyte thumbnailWidth; - ubyte thumbnailHeight; - ubyte[] thumbnail; - } - - struct DQT - { - ubyte precision; - ubyte tableId; - ubyte[] table; - } - - struct SOF0Component - { - ubyte hsubsamling; - ubyte vsubsamling; - ubyte dqtTableId; - } - - struct SOF0 - { - ubyte precision; - ushort height; - ushort width; - ubyte componentsNum; - SOF0Component[] components; - } - - struct DHT - { - ubyte clas; - ubyte tableId; - ubyte[HuffmanCode] huffmanTable; - HuffmanTreeNode* huffmanTree; - } - - struct SOSComponent - { - ubyte tableIdDC; - ubyte tableIdAC; - } - - struct SOS - { - ubyte componentsNum; - SOSComponent[] components; - ubyte spectralSelectionStart; - ubyte spectralSelectionEnd; - ubyte successiveApproximationBitHigh; - ubyte successiveApproximationBitLow; - ubyte[] encodedData; - } - - JFIF jfif; - DQT[] dqt; - SOF0 sof0; - DHT[] dht; - SOS sos; - - DQT* getQuantizationTable(ubyte id) - { - foreach(ref t; dqt) - if (t.tableId == id) - return &t; - return null; - } - - DHT* getHuffmanTable(ubyte clas, ubyte id) - { - foreach(ref t; dht) - if (t.clas == clas && - t.tableId == id) - return &t; - return null; - } -} - -enum JPEGMarkerType -{ - Unknown, - SOI, - SOF0, - SOF1, - SOF2, - DHT, - DQT, - DRI, - SOS, - RSTn, - APP0, - APP1, - COM, - EOI -} - -SuperImage loadJPEG(string filename) -{ - return loadJPEG(openForInput(filename)); -} - -SuperImage loadJPEG(InputStream istrm) -{ - auto jpg = new JPEGImage(); - - while (istrm.readable) - { - JPEGMarkerType mt = readMarker(istrm, jpg); - } - - return decodeScanData(jpg); -} - -JPEGMarkerType readMarker(InputStream istrm, JPEGImage jpg) -{ - JPEGMarkerType mt; - ushort magic = istrm.readNumeric!ushort(Endian.Big); - - switch (magic) - { - case 0xFFD8: - mt = JPEGMarkerType.SOI; - version(JPEGDebug) writeln("SOI"); - break; - - case 0xFFE0: - mt = JPEGMarkerType.APP0; - readJFIF(istrm, jpg); - break; - - case 0xFFE1: - mt = JPEGMarkerType.APP1; - readEXIF(istrm, jpg); - break; - - case 0xFFDB: - mt = JPEGMarkerType.DQT; - readDQT(istrm, jpg); - break; - - case 0xFFC0: - mt = JPEGMarkerType.SOF0; - readSOF0(istrm, jpg); - break; - - case 0xFFC2: - mt = JPEGMarkerType.SOF2; - throw new Exception("Progressive JPEG is not supported"); - break; - - case 0xFFC4: - mt = JPEGMarkerType.DHT; - readDHT(istrm, jpg); - break; - - case 0xFFDA: - mt = JPEGMarkerType.SOS; - readSOS(istrm, jpg); - break; - - default: - break; - } - - return mt; -} - -void readJFIF(InputStream istrm, JPEGImage jpg) -{ - ushort jfif_length = istrm.readNumeric!ushort(Endian.Big); - - char[5] jfif_id = istrm.readChars!5; - if (jfif_id != "JFIF\0") - throw new Exception("Illegal JFIF header"); - - jpg.jfif.versionMajor = istrm.readNumeric!ubyte; - jpg.jfif.versionMinor = istrm.readNumeric!ubyte; - jpg.jfif.units = istrm.readNumeric!ubyte; - jpg.jfif.xdensity = istrm.readNumeric!ushort(Endian.Big); - jpg.jfif.ydensity = istrm.readNumeric!ushort(Endian.Big); - jpg.jfif.thumbnailWidth = istrm.readNumeric!ubyte; - jpg.jfif.thumbnailHeight = istrm.readNumeric!ubyte; - - uint jfif_thumb_length = jpg.jfif.thumbnailWidth * jpg.jfif.thumbnailHeight * 3; - if (jfif_thumb_length > 0) - { - jpg.jfif.thumbnail = new ubyte[jfif_thumb_length]; - istrm.readBytes(jpg.jfif.thumbnail.ptr, jfif_thumb_length); - } - - version(JPEGDebug) - { - writefln("APP0/JFIF length: %s", jfif_length); - writefln("APP0/JFIF identifier: %s", jfif_id); - writefln("APP0/JFIF version major: %s", jpg.jfif.versionMajor); - writefln("APP0/JFIF version minor: %s", jpg.jfif.versionMinor); - writefln("APP0/JFIF units: %s", jpg.jfif.units); - writefln("APP0/JFIF xdensity: %s", jpg.jfif.xdensity); - writefln("APP0/JFIF ydensity: %s", jpg.jfif.ydensity); - writefln("APP0/JFIF xthumbnail: %s", jpg.jfif.thumbnailWidth); - writefln("APP0/JFIF ythumbnail: %s", jpg.jfif.thumbnailHeight); - } -} - -void readEXIF(InputStream istrm, JPEGImage jpg) -{ - ushort exif_length = istrm.readNumeric!ushort(Endian.Big); - // TODO: interpret JFIF data - ubyte[] exif = new ubyte[exif_length-2]; - istrm.readBytes(exif.ptr, exif_length-2); - version(JPEGDebug) - { - writefln("APP1/EXIF length: %s", exif_length); - } -} - -void readDQT(InputStream istrm, JPEGImage jpg) -{ - ushort dqt_length = istrm.readNumeric!ushort(Endian.Big); - version(JPEGDebug) - { - writefln("DQT length: %s", dqt_length); - } - - dqt_length -= 2; - - while(dqt_length) - { - JPEGImage.DQT dqt; - jpg.dqt ~= dqt; - - ubyte bite = istrm.readNumeric!ubyte; - jpg.dqt[$-1].precision = bite.hiNibble; - jpg.dqt[$-1].tableId = bite.loNibble; - - dqt_length--; - - if (jpg.dqt[$-1].precision == 0) - { - jpg.dqt[$-1].table = new ubyte[64]; - dqt_length -= 64; - } - else if (jpg.dqt[$-1].precision == 1) - { - jpg.dqt[$-1].table = new ubyte[64*2]; - dqt_length -= 128; - } - - istrm.readBytes(jpg.dqt[$-1].table.ptr, jpg.dqt[$-1].table.length); - - version(JPEGDebug) - { - writefln("DQT precision: %s", jpg.dqt[$-1].precision); - writefln("DQT table id: %s", jpg.dqt[$-1].tableId); - writefln("DQT table: %s", jpg.dqt[$-1].table); - } - } -} - -void readSOF0(InputStream istrm, JPEGImage jpg) -{ - ushort sof0_length = istrm.readNumeric!ushort(Endian.Big); - jpg.sof0.precision = istrm.readNumeric!ubyte; - jpg.sof0.height = istrm.readNumeric!ushort(Endian.Big); - jpg.sof0.width = istrm.readNumeric!ushort(Endian.Big); - jpg.sof0.componentsNum = istrm.readNumeric!ubyte; - - version(JPEGDebug) - { - writefln("SOF0 length: %s", sof0_length); - writefln("SOF0 precision: %s", jpg.sof0.precision); - writefln("SOF0 height: %s", jpg.sof0.height); - writefln("SOF0 width: %s", jpg.sof0.width); - writefln("SOF0 components: %s", jpg.sof0.componentsNum); - } - - jpg.sof0.components = new JPEGImage.SOF0Component[jpg.sof0.componentsNum]; - - foreach(ref c; jpg.sof0.components) - { - ubyte c_id = istrm.readNumeric!ubyte; - ubyte bite = istrm.readNumeric!ubyte; - c.hsubsamling = bite.hiNibble; - c.vsubsamling = bite.loNibble; - c.dqtTableId = istrm.readNumeric!ubyte; - version(JPEGDebug) - { - writefln("SOF0 component id: %s", c_id); - writefln("SOF0 component %s hsubsamling: %s", c_id, c.hsubsamling); - writefln("SOF0 component %s vsubsamling: %s", c_id, c.vsubsamling); - writefln("SOF0 component %s table id: %s", c_id, c.dqtTableId); - } - } -} - -void readDHT(InputStream istrm, JPEGImage jpg) -{ - ushort dht_length = istrm.readNumeric!ushort(Endian.Big); - version(JPEGDebug) - { - writefln("DHT length: %s", dht_length); - } - - dht_length -= 2; - - while(dht_length > 0) - { - JPEGImage.DHT dht; - jpg.dht ~= dht; - - ubyte bite = istrm.readNumeric!ubyte; - dht_length--; - jpg.dht[$-1].clas = bite.hiNibble; - jpg.dht[$-1].tableId = bite.loNibble; - - ubyte[16] dht_code_lengths; - istrm.readBytes(dht_code_lengths.ptr, 16); - dht_length -= 16; - - version(JPEGDebug) - { - writefln("DHT class: %s (%s)", - jpg.dht[$-1].clas, - jpg.dht[$-1].clas? "AC":"DC"); - writefln("DHT tableId: %s", jpg.dht[$-1].tableId); - writefln("DHT Huffman code lengths: %s", dht_code_lengths); - } - - // Read Huffman table - int totalCodes = reduce!("a + b")(0, dht_code_lengths); - int storedCodes = 0; - ubyte treeLevel = 0; - ushort bits = 0; - - while (storedCodes != totalCodes) - { - while (treeLevel < 15 && - dht_code_lengths[treeLevel] == 0) - { - treeLevel++; - bits *= 2; - } - - if (treeLevel < 16) - { - uint bitsNum = treeLevel + 1; - HuffmanCode code = HuffmanCode(bits, bitsNum); - jpg.dht[$-1].huffmanTable[code] = istrm.readNumeric!ubyte; - dht_length--; - - storedCodes++; - bits++; - dht_code_lengths[treeLevel]--; - } - } - - version(JPEGDebug) - { - //writeln("DHT Huffman table:\n", jpg.dht[$-1].huffmanTable); - /* - auto huffmanSymbols = jpg.dht[$-1].huffmanTable.values; - huffmanSymbols.sort(); - foreach(v; huffmanSymbols) - writef("%x ", v); - writef("\n"); - */ - } - - jpg.dht[$-1].huffmanTree = treeFromTable(jpg.dht[$-1].huffmanTable); - string[ubyte] table; - jpg.dht[$-1].huffmanTree.getCodes(table); - - version(JPEGDebug) - { - //writeln(table); - } - } -} - -void readSOS(InputStream istrm, JPEGImage jpg) -{ - ushort sos_length = istrm.readNumeric!ushort(Endian.Big); - jpg.sos.componentsNum = istrm.readNumeric!ubyte; - - version(JPEGDebug) - { - writefln("SOS length: %s", sos_length); - writefln("SOS components: %s", jpg.sos.componentsNum); - } - - jpg.sos.components = new JPEGImage.SOSComponent[jpg.sos.componentsNum]; - - foreach(ref c; jpg.sos.components) - { - ubyte c_id = istrm.readNumeric!ubyte; - ubyte bite = istrm.readNumeric!ubyte; - c.tableIdDC = bite.hiNibble; - c.tableIdAC = bite.loNibble; - version(JPEGDebug) - { - writefln("SOS component id: %s", c_id); - writefln("SOS component %s DC table id: %s", c_id, c.tableIdDC); - writefln("SOS component %s AC table id: %s", c_id, c.tableIdAC); - } - } - - jpg.sos.spectralSelectionStart = istrm.readNumeric!ubyte; - jpg.sos.spectralSelectionEnd = istrm.readNumeric!ubyte; - ubyte bite = istrm.readNumeric!ubyte; - jpg.sos.successiveApproximationBitHigh = bite.hiNibble; - jpg.sos.successiveApproximationBitLow = bite.loNibble; - - version(JPEGDebug) - { - writefln("SOS spectral selection start: %s", jpg.sos.spectralSelectionStart); - writefln("SOS spectral selection end: %s", jpg.sos.spectralSelectionEnd); - writefln("SOS successive approximation bit: %s", jpg.sos.successiveApproximationBitHigh); - writefln("SOS successive approximation bit low: %s", jpg.sos.successiveApproximationBitLow); - } - - // TODO: don't copy scan data, just decode on the fly - uint bytesRead = 0; - ubyte prev = 0x00; - bool endMarkerFound = false; - while (istrm.readable && !endMarkerFound) - { - bite = istrm.readNumeric!ubyte; - bytesRead++; - - endMarkerFound = (prev == 0xFF && bite == 0xD9); - - if (!endMarkerFound) - { -/* - // TODO: is this needed? - if (bite != 0xFF) - { - ubyte datum = bite; - - if (prev == 0xFF) - { - if (bite == 0x00) - datum = 0xFF; - else - { - writefln("Found marker: %X, %X", prev, bite); - prev = 0x00; - continue; - } - } - - jpg.sos.encodedData ~= datum; - } - - prev = bite; -*/ - jpg.sos.encodedData ~= bite; - prev = bite; - } - } - - version(JPEGDebug) - { - writefln("Bytes read: %s", bytesRead); - writefln("SOS encoded data length: %s", jpg.sos.encodedData.length); - } -} - -/* - * Decodes compressed data and creates RGB image from it - */ -SuperImage decodeScanData(JPEGImage jpg) -{ - if (jpg.sos.encodedData.length == 0) - throw new Exception("No data found"); - - uint bytePos = 0; - uint bitPos = 0; - - // Huffman decode a byte from SCAN bit stream - ubyte decodeByte(HuffmanTreeNode* node) - { - while(!node.isLeaf) - { - ubyte b = jpg.sos.encodedData[bytePos]; - - bool bit = getBit(b, 7-bitPos); - bitPos++; - if (bitPos == 8) - { - bitPos = 0; - bytePos++; - if (b == 0xFF) - { - b = jpg.sos.encodedData[bytePos]; - if (b == 0x00) - bytePos++; - } - } - - if (bit) - node = node.right; - else - node = node.left; - - if (node is null) - throw new Exception("No Huffman code found"); - } - - return node.ch; - } - - // Read len bits from stream to buffer - uint readBits(ubyte len) - { - uint buffer = 0; - uint i = 0; - uint by = 0; - uint bi = 0; - - while (i < len) - { - ubyte b = jpg.sos.encodedData[bytePos]; - - bool bit = getBit(b, 7-bitPos); - buffer = setBit(buffer, (by * 8 + bi), bit); - - bi++; - if (bi == 8) - { - bi = 0; - by++; - } - - i++; - - bitPos++; - if (bitPos == 8) - { - bitPos = 0; - bytePos++; - if (b == 0xFF) - { - b = jpg.sos.encodedData[bytePos]; - if (b == 0x00) - bytePos++; - } - } - } - - return buffer; - } - - // Decode DCT coefficient from bit buffer - int decodeCoef(uint buffer, ubyte numBits) - { - bool positive = getBit(buffer, 0); - - int value = 0; - foreach(j; 0..numBits) - { - bool bit = getBit(buffer, numBits-1-j); - value = setBit(value, j, bit); - } - - if (positive) - return value; - else - return value - 2^^numBits + 1; - } - - static const ubyte dezigzag[64] = - [ - 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 - ]; - - // Store previous DC coefficients - // TODO: support more channels? - if (jpg.sos.componentsNum != 3) - throw new Exception( - format("Unsupported number of components: %s", - jpg.sos.componentsNum)); - int[3] dcCoefPrev; - - int[8*8][4] y_matrix; - int[8*8] cb_matrix; - int[8*8] cr_matrix; - - // TODO: support other subsampling types - uint numMCUsH = jpg.sof0.width / 16 + ((jpg.sof0.width % 16) > 0); - uint numMCUsV = jpg.sof0.height / 16 + ((jpg.sof0.height % 16) > 0); - auto img = new ImageRGB8(jpg.sof0.width, jpg.sof0.height); - - if (jpg.dqt.length == 0) - throw new Exception("No DQTs found"); - - // Read MCUs - foreach(mcuY; 0..numMCUsV) - foreach(mcuX; 0..numMCUsH) - { - // Read MCU for each channel - foreach(ci, ref c; jpg.sos.components) - { - auto tableDC = jpg.getHuffmanTable(0, c.tableIdDC); - auto tableAC = jpg.getHuffmanTable(1, c.tableIdAC); - - if (tableDC is null) - throw new Exception("Illegal DC table index in MCU component"); - if (tableAC is null) - throw new Exception("Illegal AC table index in MCU component"); - - auto component = jpg.sof0.components[ci]; - auto hblocks = component.hsubsamling; - auto vblocks = component.vsubsamling; - auto dqtTableId = component.dqtTableId; - - if (ci == 0) // Y channel - { - if (hblocks != 2 && vblocks != 2) - throw new Exception( - "Unsupported subsampling type, only 4:2:0 is supported"); - } - else if (ci == 1 || ci == 2) // Cb or Cr channel - { - if (hblocks != 1 && vblocks != 1) - throw new Exception( - "Unsupported subsampling type, only 4:2:0 is supported"); - } - - // Read 8x8 blocks - foreach(by; 0..vblocks) - foreach(bx; 0..hblocks) - { - int[8*8] block; - - // Read DC coefficient - ubyte dcDiffLen = decodeByte(tableDC.huffmanTree); - - if (dcDiffLen > 0) - { - uint dcBuffer = readBits(dcDiffLen); - dcCoefPrev[ci] += decodeCoef(dcBuffer, dcDiffLen); - } - - block[0] = dcCoefPrev[ci]; - - // Read AC coefficients - { - uint i = 1; - bool eob = false; - while (!eob && i < 64) - { - ubyte code = decodeByte(tableAC.huffmanTree); - - if (code == 0x00) // EOB, all next values are zero - eob = true; - else if (code == 0xF0) // ZRL, next 16 values are zero - { - foreach(j; 0..16) - if (i < 64) - { - block[i] = 0; - i++; - } - } - else - { - ubyte hi = hiNibble(code); - ubyte lo = loNibble(code); - - uint zeroes = hi; - foreach(j; 0..zeroes) - if (i < 64) - { - block[i] = 0; - i++; - } - - int acCoef = 0; - if (lo > 0) - { - uint acBuffer = readBits(lo); - acCoef = decodeCoef(acBuffer, lo); - } - - if (i < 64) - block[i] = acCoef; - - i++; - } - } - } - - // Multiply block by quantization matrix - foreach(i, ref v; block) - v *= jpg.dqt[dqtTableId].table[i]; - - // Convert matrix from zig-zag order to normal order - int[8*8] dctMatrix; - - foreach(i, v; block) - dctMatrix[dezigzag[i]] = v; - - idct64(dctMatrix.ptr); - - int* outMatrixPtr; - if (ci == 0) - outMatrixPtr = y_matrix[by * hblocks + bx].ptr; - else if (ci == 1) - outMatrixPtr = cb_matrix.ptr; - else if (ci == 2) - outMatrixPtr = cr_matrix.ptr; - else - throw new Exception("Illegal component index"); - - for(uint i = 0; i < 64; i++) - outMatrixPtr[i] = dctMatrix[i]; - } - } - - // Convert MCU from YCbCr to RGB - // TODO: support other subsampling types - - foreach(y; 0..16) // Pixel coordinates in MCU - foreach(x; 0..16) - { - // Y block coordinates - uint bx = x / 8; - uint by = y / 8; - uint y_block = by * 2 + bx; - - // Pixel coordinates in Y block - uint i = y - by * 8; - uint j = x - bx * 8; - - // Pixel coordinates in Cb/Cr block - uint yy = y / 2; - uint xx = x / 2; - - float Y = cast(float)y_matrix[y_block][i * 8 + j] + 128.0f; - float Cb = cast(float)cb_matrix[yy * 8 + xx]; - float Cr = cast(float)cr_matrix[yy * 8 + xx]; - - Color4f col; - col.r = Y + 1.402f * Cr; - col.g = Y - 0.34414f * Cb - 0.71414f * Cr; - col.b = Y + 1.772f * Cb; - - col = col / 255.0f; - col.a = 1.0f; - - // Pixel coordinates in image - uint ix = mcuX * 16 + x; - uint iy = mcuY * 16 + y; - if (ix < img.width && iy < img.height) - img[ix, img.height - 1 - iy] = col; - } - } - - return img; -} - +*/ + +module dlib.image.io.jpeg; + +private +{ + import std.stdio; + import std.algorithm; + import std.math; + import std.string; + + import dlib.core.stream; + import dlib.filesystem.local; + import dlib.image.color; + import dlib.image.image; + + import dlib.image.io.bitio; + import dlib.image.io.huffman; + import dlib.image.io.idct; +} + +/* + * Simple JPEG decoder + * + * Limitations: + * - Doesn't support progressive JPEG + * - Supports only 4:2:0 subsampling + * - Doesn't perform chroma interpolation + * - Doesn't read EXIF metadata + */ + +// Uncomment this to see debug messages +//version = JPEGDebug; + +T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) +if (is(T == ubyte)) +{ + ubyte b; + istrm.readBytes(&b, 1); + return b; +} + +T readNumeric(T) (InputStream istrm, Endian endian = Endian.Little) +if (is(T == ushort)) +{ + union U16 + { + ubyte[2] asBytes; + ushort asUshort; + } + U16 u16; + istrm.readBytes(u16.asBytes.ptr, 2); + version(LittleEndian) + { + if (endian == Endian.Big) + return u16.asUshort.swapEndian16; + else + return u16.asUshort; + } + else + { + if (endian == Endian.Little) + return u16.asUshort.swapEndian16; + else + return u16.asUshort; + } +} + +char[size] readChars(size_t size) (InputStream istrm) +{ + char[size] chars; + istrm.readBytes(chars.ptr, size); + return chars; +} + +//TODO: make this struct +class JPEGImage +{ + struct JFIF + { + ubyte versionMajor; + ubyte versionMinor; + ubyte units; + ushort xdensity; + ushort ydensity; + ubyte thumbnailWidth; + ubyte thumbnailHeight; + ubyte[] thumbnail; + } + + struct DQT + { + ubyte precision; + ubyte tableId; + ubyte[] table; + } + + struct SOF0Component + { + ubyte hsubsamling; + ubyte vsubsamling; + ubyte dqtTableId; + } + + struct SOF0 + { + ubyte precision; + ushort height; + ushort width; + ubyte componentsNum; + SOF0Component[] components; + } + + struct DHT + { + ubyte clas; + ubyte tableId; + ubyte[HuffmanCode] huffmanTable; + HuffmanTreeNode* huffmanTree; + } + + struct SOSComponent + { + ubyte tableIdDC; + ubyte tableIdAC; + } + + struct SOS + { + ubyte componentsNum; + SOSComponent[] components; + ubyte spectralSelectionStart; + ubyte spectralSelectionEnd; + ubyte successiveApproximationBitHigh; + ubyte successiveApproximationBitLow; + ubyte[] encodedData; + } + + JFIF jfif; + DQT[] dqt; + SOF0 sof0; + DHT[] dht; + SOS sos; + + DQT* getQuantizationTable(ubyte id) + { + foreach(ref t; dqt) + if (t.tableId == id) + return &t; + return null; + } + + DHT* getHuffmanTable(ubyte clas, ubyte id) + { + foreach(ref t; dht) + if (t.clas == clas && + t.tableId == id) + return &t; + return null; + } +} + +enum JPEGMarkerType +{ + Unknown, + SOI, + SOF0, + SOF1, + SOF2, + DHT, + DQT, + DRI, + SOS, + RSTn, + APP0, + APP1, + COM, + EOI +} + +SuperImage loadJPEG(string filename) +{ + return loadJPEG(openForInput(filename)); +} + +SuperImage loadJPEG(InputStream istrm) +{ + auto jpg = new JPEGImage(); + + while (istrm.readable) + { + JPEGMarkerType mt = readMarker(istrm, jpg); + } + + return decodeScanData(jpg); +} + +JPEGMarkerType readMarker(InputStream istrm, JPEGImage jpg) +{ + JPEGMarkerType mt; + ushort magic = istrm.readNumeric!ushort(Endian.Big); + + switch (magic) + { + case 0xFFD8: + mt = JPEGMarkerType.SOI; + version(JPEGDebug) writeln("SOI"); + break; + + case 0xFFE0: + mt = JPEGMarkerType.APP0; + readJFIF(istrm, jpg); + break; + + case 0xFFE1: + mt = JPEGMarkerType.APP1; + readEXIF(istrm, jpg); + break; + + case 0xFFDB: + mt = JPEGMarkerType.DQT; + readDQT(istrm, jpg); + break; + + case 0xFFC0: + mt = JPEGMarkerType.SOF0; + readSOF0(istrm, jpg); + break; + + case 0xFFC2: + mt = JPEGMarkerType.SOF2; + throw new Exception("Progressive JPEG is not supported"); + + case 0xFFC4: + mt = JPEGMarkerType.DHT; + readDHT(istrm, jpg); + break; + + case 0xFFDA: + mt = JPEGMarkerType.SOS; + readSOS(istrm, jpg); + break; + + default: + break; + } + + return mt; +} + +void readJFIF(InputStream istrm, JPEGImage jpg) +{ + ushort jfif_length = istrm.readNumeric!ushort(Endian.Big); + + char[5] jfif_id = istrm.readChars!5; + if (jfif_id != "JFIF\0") + throw new Exception("Illegal JFIF header"); + + jpg.jfif.versionMajor = istrm.readNumeric!ubyte; + jpg.jfif.versionMinor = istrm.readNumeric!ubyte; + jpg.jfif.units = istrm.readNumeric!ubyte; + jpg.jfif.xdensity = istrm.readNumeric!ushort(Endian.Big); + jpg.jfif.ydensity = istrm.readNumeric!ushort(Endian.Big); + jpg.jfif.thumbnailWidth = istrm.readNumeric!ubyte; + jpg.jfif.thumbnailHeight = istrm.readNumeric!ubyte; + + uint jfif_thumb_length = jpg.jfif.thumbnailWidth * jpg.jfif.thumbnailHeight * 3; + if (jfif_thumb_length > 0) + { + jpg.jfif.thumbnail = new ubyte[jfif_thumb_length]; + istrm.readBytes(jpg.jfif.thumbnail.ptr, jfif_thumb_length); + } + + version(JPEGDebug) + { + writefln("APP0/JFIF length: %s", jfif_length); + writefln("APP0/JFIF identifier: %s", jfif_id); + writefln("APP0/JFIF version major: %s", jpg.jfif.versionMajor); + writefln("APP0/JFIF version minor: %s", jpg.jfif.versionMinor); + writefln("APP0/JFIF units: %s", jpg.jfif.units); + writefln("APP0/JFIF xdensity: %s", jpg.jfif.xdensity); + writefln("APP0/JFIF ydensity: %s", jpg.jfif.ydensity); + writefln("APP0/JFIF xthumbnail: %s", jpg.jfif.thumbnailWidth); + writefln("APP0/JFIF ythumbnail: %s", jpg.jfif.thumbnailHeight); + } +} + +void readEXIF(InputStream istrm, JPEGImage jpg) +{ + ushort exif_length = istrm.readNumeric!ushort(Endian.Big); + // TODO: interpret JFIF data + ubyte[] exif = new ubyte[exif_length-2]; + istrm.readBytes(exif.ptr, exif_length-2); + version(JPEGDebug) + { + writefln("APP1/EXIF length: %s", exif_length); + } +} + +void readDQT(InputStream istrm, JPEGImage jpg) +{ + ushort dqt_length = istrm.readNumeric!ushort(Endian.Big); + version(JPEGDebug) + { + writefln("DQT length: %s", dqt_length); + } + + dqt_length -= 2; + + while(dqt_length) + { + JPEGImage.DQT dqt; + jpg.dqt ~= dqt; + + ubyte bite = istrm.readNumeric!ubyte; + jpg.dqt[$-1].precision = bite.hiNibble; + jpg.dqt[$-1].tableId = bite.loNibble; + + dqt_length--; + + if (jpg.dqt[$-1].precision == 0) + { + jpg.dqt[$-1].table = new ubyte[64]; + dqt_length -= 64; + } + else if (jpg.dqt[$-1].precision == 1) + { + jpg.dqt[$-1].table = new ubyte[64*2]; + dqt_length -= 128; + } + + istrm.readBytes(jpg.dqt[$-1].table.ptr, jpg.dqt[$-1].table.length); + + version(JPEGDebug) + { + writefln("DQT precision: %s", jpg.dqt[$-1].precision); + writefln("DQT table id: %s", jpg.dqt[$-1].tableId); + writefln("DQT table: %s", jpg.dqt[$-1].table); + } + } +} + +void readSOF0(InputStream istrm, JPEGImage jpg) +{ + ushort sof0_length = istrm.readNumeric!ushort(Endian.Big); + jpg.sof0.precision = istrm.readNumeric!ubyte; + jpg.sof0.height = istrm.readNumeric!ushort(Endian.Big); + jpg.sof0.width = istrm.readNumeric!ushort(Endian.Big); + jpg.sof0.componentsNum = istrm.readNumeric!ubyte; + + version(JPEGDebug) + { + writefln("SOF0 length: %s", sof0_length); + writefln("SOF0 precision: %s", jpg.sof0.precision); + writefln("SOF0 height: %s", jpg.sof0.height); + writefln("SOF0 width: %s", jpg.sof0.width); + writefln("SOF0 components: %s", jpg.sof0.componentsNum); + } + + jpg.sof0.components = new JPEGImage.SOF0Component[jpg.sof0.componentsNum]; + + foreach(ref c; jpg.sof0.components) + { + ubyte c_id = istrm.readNumeric!ubyte; + ubyte bite = istrm.readNumeric!ubyte; + c.hsubsamling = bite.hiNibble; + c.vsubsamling = bite.loNibble; + c.dqtTableId = istrm.readNumeric!ubyte; + version(JPEGDebug) + { + writefln("SOF0 component id: %s", c_id); + writefln("SOF0 component %s hsubsamling: %s", c_id, c.hsubsamling); + writefln("SOF0 component %s vsubsamling: %s", c_id, c.vsubsamling); + writefln("SOF0 component %s table id: %s", c_id, c.dqtTableId); + } + } +} + +void readDHT(InputStream istrm, JPEGImage jpg) +{ + ushort dht_length = istrm.readNumeric!ushort(Endian.Big); + version(JPEGDebug) + { + writefln("DHT length: %s", dht_length); + } + + dht_length -= 2; + + while(dht_length > 0) + { + JPEGImage.DHT dht; + jpg.dht ~= dht; + + ubyte bite = istrm.readNumeric!ubyte; + dht_length--; + jpg.dht[$-1].clas = bite.hiNibble; + jpg.dht[$-1].tableId = bite.loNibble; + + ubyte[16] dht_code_lengths; + istrm.readBytes(dht_code_lengths.ptr, 16); + dht_length -= 16; + + version(JPEGDebug) + { + writefln("DHT class: %s (%s)", + jpg.dht[$-1].clas, + jpg.dht[$-1].clas? "AC":"DC"); + writefln("DHT tableId: %s", jpg.dht[$-1].tableId); + writefln("DHT Huffman code lengths: %s", dht_code_lengths); + } + + // Read Huffman table + int totalCodes = reduce!("a + b")(0, dht_code_lengths); + int storedCodes = 0; + ubyte treeLevel = 0; + ushort bits = 0; + + while (storedCodes != totalCodes) + { + while (treeLevel < 15 && + dht_code_lengths[treeLevel] == 0) + { + treeLevel++; + bits *= 2; + } + + if (treeLevel < 16) + { + uint bitsNum = treeLevel + 1; + HuffmanCode code = HuffmanCode(bits, bitsNum); + jpg.dht[$-1].huffmanTable[code] = istrm.readNumeric!ubyte; + dht_length--; + + storedCodes++; + bits++; + dht_code_lengths[treeLevel]--; + } + } + + version(JPEGDebug) + { + //writeln("DHT Huffman table:\n", jpg.dht[$-1].huffmanTable); + /* + auto huffmanSymbols = jpg.dht[$-1].huffmanTable.values; + huffmanSymbols.sort(); + foreach(v; huffmanSymbols) + writef("%x ", v); + writef("\n"); + */ + } + + jpg.dht[$-1].huffmanTree = treeFromTable(jpg.dht[$-1].huffmanTable); + string[ubyte] table; + jpg.dht[$-1].huffmanTree.getCodes(table); + + version(JPEGDebug) + { + //writeln(table); + } + } +} + +void readSOS(InputStream istrm, JPEGImage jpg) +{ + ushort sos_length = istrm.readNumeric!ushort(Endian.Big); + jpg.sos.componentsNum = istrm.readNumeric!ubyte; + + version(JPEGDebug) + { + writefln("SOS length: %s", sos_length); + writefln("SOS components: %s", jpg.sos.componentsNum); + } + + jpg.sos.components = new JPEGImage.SOSComponent[jpg.sos.componentsNum]; + + foreach(ref c; jpg.sos.components) + { + ubyte c_id = istrm.readNumeric!ubyte; + ubyte bite = istrm.readNumeric!ubyte; + c.tableIdDC = bite.hiNibble; + c.tableIdAC = bite.loNibble; + version(JPEGDebug) + { + writefln("SOS component id: %s", c_id); + writefln("SOS component %s DC table id: %s", c_id, c.tableIdDC); + writefln("SOS component %s AC table id: %s", c_id, c.tableIdAC); + } + } + + jpg.sos.spectralSelectionStart = istrm.readNumeric!ubyte; + jpg.sos.spectralSelectionEnd = istrm.readNumeric!ubyte; + ubyte bite = istrm.readNumeric!ubyte; + jpg.sos.successiveApproximationBitHigh = bite.hiNibble; + jpg.sos.successiveApproximationBitLow = bite.loNibble; + + version(JPEGDebug) + { + writefln("SOS spectral selection start: %s", jpg.sos.spectralSelectionStart); + writefln("SOS spectral selection end: %s", jpg.sos.spectralSelectionEnd); + writefln("SOS successive approximation bit: %s", jpg.sos.successiveApproximationBitHigh); + writefln("SOS successive approximation bit low: %s", jpg.sos.successiveApproximationBitLow); + } + + // TODO: don't copy scan data, just decode on the fly + uint bytesRead = 0; + ubyte prev = 0x00; + bool endMarkerFound = false; + while (istrm.readable && !endMarkerFound) + { + bite = istrm.readNumeric!ubyte; + bytesRead++; + + endMarkerFound = (prev == 0xFF && bite == 0xD9); + + if (!endMarkerFound) + { +/* + // TODO: is this needed? + if (bite != 0xFF) + { + ubyte datum = bite; + + if (prev == 0xFF) + { + if (bite == 0x00) + datum = 0xFF; + else + { + writefln("Found marker: %X, %X", prev, bite); + prev = 0x00; + continue; + } + } + + jpg.sos.encodedData ~= datum; + } + + prev = bite; +*/ + jpg.sos.encodedData ~= bite; + prev = bite; + } + } + + version(JPEGDebug) + { + writefln("Bytes read: %s", bytesRead); + writefln("SOS encoded data length: %s", jpg.sos.encodedData.length); + } +} + +/* + * Decodes compressed data and creates RGB image from it + */ +SuperImage decodeScanData(JPEGImage jpg) +{ + if (jpg.sos.encodedData.length == 0) + throw new Exception("No data found"); + + uint bytePos = 0; + uint bitPos = 0; + + // Huffman decode a byte from SCAN bit stream + ubyte decodeByte(HuffmanTreeNode* node) + { + while(!node.isLeaf) + { + ubyte b = jpg.sos.encodedData[bytePos]; + + bool bit = getBit(b, 7-bitPos); + bitPos++; + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + if (b == 0xFF) + { + b = jpg.sos.encodedData[bytePos]; + if (b == 0x00) + bytePos++; + } + } + + if (bit) + node = node.right; + else + node = node.left; + + if (node is null) + throw new Exception("No Huffman code found"); + } + + return node.ch; + } + + // Read len bits from stream to buffer + uint readBits(ubyte len) + { + uint buffer = 0; + uint i = 0; + uint by = 0; + uint bi = 0; + + while (i < len) + { + ubyte b = jpg.sos.encodedData[bytePos]; + + bool bit = getBit(b, 7-bitPos); + buffer = setBit(buffer, (by * 8 + bi), bit); + + bi++; + if (bi == 8) + { + bi = 0; + by++; + } + + i++; + + bitPos++; + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + if (b == 0xFF) + { + b = jpg.sos.encodedData[bytePos]; + if (b == 0x00) + bytePos++; + } + } + } + + return buffer; + } + + // Decode DCT coefficient from bit buffer + int decodeCoef(uint buffer, ubyte numBits) + { + bool positive = getBit(buffer, 0); + + int value = 0; + foreach(j; 0..numBits) + { + bool bit = getBit(buffer, numBits-1-j); + value = setBit(value, j, bit); + } + + if (positive) + return value; + else + return value - 2^^numBits + 1; + } + + static const ubyte dezigzag[64] = + [ + 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 + ]; + + // Store previous DC coefficients + // TODO: support more channels? + if (jpg.sos.componentsNum != 3) + throw new Exception( + format("Unsupported number of components: %s", + jpg.sos.componentsNum)); + int[3] dcCoefPrev; + + int[8*8][4] y_matrix; + int[8*8] cb_matrix; + int[8*8] cr_matrix; + + // TODO: support other subsampling types + uint numMCUsH = jpg.sof0.width / 16 + ((jpg.sof0.width % 16) > 0); + uint numMCUsV = jpg.sof0.height / 16 + ((jpg.sof0.height % 16) > 0); + auto img = new ImageRGB8(jpg.sof0.width, jpg.sof0.height); + + if (jpg.dqt.length == 0) + throw new Exception("No DQTs found"); + + // Read MCUs + foreach(mcuY; 0..numMCUsV) + foreach(mcuX; 0..numMCUsH) + { + // Read MCU for each channel + foreach(ci, ref c; jpg.sos.components) + { + auto tableDC = jpg.getHuffmanTable(0, c.tableIdDC); + auto tableAC = jpg.getHuffmanTable(1, c.tableIdAC); + + if (tableDC is null) + throw new Exception("Illegal DC table index in MCU component"); + if (tableAC is null) + throw new Exception("Illegal AC table index in MCU component"); + + auto component = jpg.sof0.components[ci]; + auto hblocks = component.hsubsamling; + auto vblocks = component.vsubsamling; + auto dqtTableId = component.dqtTableId; + + if (ci == 0) // Y channel + { + if (hblocks != 2 && vblocks != 2) + throw new Exception( + "Unsupported subsampling type, only 4:2:0 is supported"); + } + else if (ci == 1 || ci == 2) // Cb or Cr channel + { + if (hblocks != 1 && vblocks != 1) + throw new Exception( + "Unsupported subsampling type, only 4:2:0 is supported"); + } + + // Read 8x8 blocks + foreach(by; 0..vblocks) + foreach(bx; 0..hblocks) + { + int[8*8] block; + + // Read DC coefficient + ubyte dcDiffLen = decodeByte(tableDC.huffmanTree); + + if (dcDiffLen > 0) + { + uint dcBuffer = readBits(dcDiffLen); + dcCoefPrev[ci] += decodeCoef(dcBuffer, dcDiffLen); + } + + block[0] = dcCoefPrev[ci]; + + // Read AC coefficients + { + uint i = 1; + bool eob = false; + while (!eob && i < 64) + { + ubyte code = decodeByte(tableAC.huffmanTree); + + if (code == 0x00) // EOB, all next values are zero + eob = true; + else if (code == 0xF0) // ZRL, next 16 values are zero + { + foreach(j; 0..16) + if (i < 64) + { + block[i] = 0; + i++; + } + } + else + { + ubyte hi = hiNibble(code); + ubyte lo = loNibble(code); + + uint zeroes = hi; + foreach(j; 0..zeroes) + if (i < 64) + { + block[i] = 0; + i++; + } + + int acCoef = 0; + if (lo > 0) + { + uint acBuffer = readBits(lo); + acCoef = decodeCoef(acBuffer, lo); + } + + if (i < 64) + block[i] = acCoef; + + i++; + } + } + } + + // Multiply block by quantization matrix + foreach(i, ref v; block) + v *= jpg.dqt[dqtTableId].table[i]; + + // Convert matrix from zig-zag order to normal order + int[8*8] dctMatrix; + + foreach(i, v; block) + dctMatrix[dezigzag[i]] = v; + + idct64(dctMatrix.ptr); + + int* outMatrixPtr; + if (ci == 0) + outMatrixPtr = y_matrix[by * hblocks + bx].ptr; + else if (ci == 1) + outMatrixPtr = cb_matrix.ptr; + else if (ci == 2) + outMatrixPtr = cr_matrix.ptr; + else + throw new Exception("Illegal component index"); + + for(uint i = 0; i < 64; i++) + outMatrixPtr[i] = dctMatrix[i]; + } + } + + // Convert MCU from YCbCr to RGB + // TODO: support other subsampling types + + foreach(y; 0..16) // Pixel coordinates in MCU + foreach(x; 0..16) + { + // Y block coordinates + uint bx = x / 8; + uint by = y / 8; + uint y_block = by * 2 + bx; + + // Pixel coordinates in Y block + uint i = y - by * 8; + uint j = x - bx * 8; + + // Pixel coordinates in Cb/Cr block + uint yy = y / 2; + uint xx = x / 2; + + float Y = cast(float)y_matrix[y_block][i * 8 + j] + 128.0f; + float Cb = cast(float)cb_matrix[yy * 8 + xx]; + float Cr = cast(float)cr_matrix[yy * 8 + xx]; + + Color4f col; + col.r = Y + 1.402f * Cr; + col.g = Y - 0.34414f * Cb - 0.71414f * Cr; + col.b = Y + 1.772f * Cb; + + col = col / 255.0f; + col.a = 1.0f; + + // Pixel coordinates in image + uint ix = mcuX * 16 + x; + uint iy = mcuY * 16 + y; + if (ix < img.width && iy < img.height) + img[ix, img.height - 1 - iy] = col; + } + } + + return img; +} + From 4db9530947f1d6cbcb43ae34c1ae1601c0778b70 Mon Sep 17 00:00:00 2001 From: Vadim Lopatin Date: Tue, 30 Dec 2014 15:07:06 +0400 Subject: [PATCH 2/2] fix build failure on 64 bit platforms --- dlib/image/io/huffman.d | 660 ++++++++++++++++++++-------------------- dlib/math/matrix.d | 2 +- 2 files changed, 331 insertions(+), 331 deletions(-) diff --git a/dlib/image/io/huffman.d b/dlib/image/io/huffman.d index e436acc7..760c4c2e 100644 --- a/dlib/image/io/huffman.d +++ b/dlib/image/io/huffman.d @@ -24,333 +24,333 @@ SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -*/ - -module dlib.image.io.huffman; - -private -{ - import std.stdio; - import std.algorithm; - import std.array; - import dlib.image.io.bitio; -} - -struct HuffmanTreeNode -{ - HuffmanTreeNode* parent; - HuffmanTreeNode* left; - HuffmanTreeNode* right; - ubyte ch; - uint freq; - bool blank = true; - - this( - HuffmanTreeNode* leftNode, - HuffmanTreeNode* rightNode, - ubyte symbol, - uint frequency, - bool isBlank) - { - parent = null; - left = leftNode; - right = rightNode; - - if (left !is null) - left.parent = &this; - if (right !is null) - right.parent = &this; - - ch = symbol; - freq = frequency; - blank = isBlank; - } - - bool isLeaf() - { - return (left is null && right is null); - } - - void getCodes(ref string[ubyte] table, string code = "") - { - if (isLeaf()) - { - table[ch] = code; - } - else - { - if (left !is null) - left.getCodes(table, code ~ '0'); - if (right !is null) - right.getCodes(table, code ~ '1'); - } - } - - void print(string indent = "") - { - writefln("%s<%s>%x", indent, freq, ch); - indent ~= " "; - if (left !is null) - left.print(indent); - if (right !is null) - right.print(indent); - } -} - -HuffmanTreeNode* buildHuffmanTree(ubyte[] data) -{ - // Count frequencies - uint[ubyte] freqs; - foreach(s; data) - { - if (s in freqs) - freqs[s] += 1; - else - freqs[s] = 1; - } - - // Sort in descending order - ubyte[] symbols = freqs.keys; - sort!((a, b) => freqs[a] > freqs[b])(symbols); - - // Create node list - auto nodeList = new HuffmanTreeNode*[symbols.length]; - foreach(i, s; symbols) - nodeList[i] = new HuffmanTreeNode(null, null, s, freqs[s], false); - - // Build tree - while (nodeList.length > 1) - { - // Pop two nodes with minimal frequencies - auto n1 = nodeList[$-1]; - auto n2 = nodeList[$-2]; - nodeList.popBack; - nodeList.popBack; - - // Insert a new parent node - uint fsum = n1.freq + n2.freq; - auto parent = new HuffmanTreeNode(n1, n2, 0, fsum, false); - nodeList ~= parent; - sort!((a, b) => a.freq > b.freq)(nodeList); - } - - auto root = nodeList[0]; - - return root; -} - -void packHuffmanTree(HuffmanTreeNode* node, BitWriter* bw) -{ - if (node.isLeaf) - { - bw.writeBit(true); - bw.writeByte(node.ch); - } - else - { - bw.writeBit(false); - packHuffmanTree(node.left, bw); - packHuffmanTree(node.right, bw); - } -} - -HuffmanTreeNode* unpackHuffmanTree(BitReader* br) -{ - if (!br.end) - { - bool bit = br.readBit(); - if (bit) - { - byte ch = br.readByte(); - return new HuffmanTreeNode(null, null, ch, 0, false); - } - else - { - HuffmanTreeNode* left = unpackHuffmanTree(br); - HuffmanTreeNode* right = unpackHuffmanTree(br); - return new HuffmanTreeNode(left, right, 0, 0, false); - } - } - else return null; -} - -ubyte[] encodeHuffman(ubyte[] data, out HuffmanTreeNode* tree) -{ - // Build Huffman tree - tree = buildHuffmanTree(data); - - // Generate binary codes - string[ubyte] huffTable; - tree.getCodes(huffTable); - - // Encode data - string bitStr; - foreach(s; data) - bitStr ~= huffTable[s]; - - // Pack bits to byte array - uint octetsLen = 0; - ubyte lastBits = 0; - if (bitStr.length == 8) - { - octetsLen = 1; - } - else if (bitStr.length > 8) - { - octetsLen = bitStr.length / 8; - lastBits = cast(ubyte)(bitStr.length % 8); - if (lastBits != 0) - octetsLen++; - } - else - { - octetsLen = 1; - lastBits = cast(ubyte)(bitStr.length); - } - - octetsLen++; - auto octets = new ubyte[octetsLen]; - octets[0] = lastBits; - - uint bitPos = 0; - uint bytePos = 1; - - foreach(bit; bitStr) - { - bool state; - if (bit == '0') - state = false; - else - state = true; - - octets[bytePos] = setBit(octets[bytePos], bitPos, state); - bitPos++; - - if (bitPos == 8) - { - bitPos = 0; - bytePos++; - } - } - - return octets; -} - -ubyte[] decodeHuffman(ubyte[] data, HuffmanTreeNode* tree) -{ - // Generate binary codes - string[ubyte] huffTable; - tree.getCodes(huffTable); - - //Unpack bits from array - ubyte[] result; - bool appendNext = true; - string code = ""; - ubyte lastBits = data[0]; - foreach(i, b; data[1..$]) - { - uint len; - if ((lastBits != 0) && (i == data.length-1)) - len = lastBits; - else - len = 8; - - foreach(bp; 0..len) - { - char bitChr = getBit(b, bp)? '1':'0'; - if (appendNext) - { - code ~= bitChr; - foreach(key, val; huffTable) - { - if (code == val) - { - result ~= key; - appendNext = false; - break; - } - } - } - else - { - code = ""; - code ~= bitChr; - appendNext = true; - } - } - } - - return result; -} - -struct HuffmanCode -{ - ushort bits; - uint length; - - string toString() - { - return bitString(bits, length); - } -} - -void treeAddCode(HuffmanTreeNode* root, HuffmanCode code, ubyte value) -{ - HuffmanTreeNode* node = root; - foreach(bit; code.toString) - { - if (bit == '0') - { - if (node.left is null) - { - node.left = new HuffmanTreeNode(null, null, 0, 0, false); - node.left.parent = node; - } - - node = node.left; - } - else if (bit == '1') - { - if (node.right is null) - { - node.right = new HuffmanTreeNode(null, null, 0, 0, false); - node.right.parent = node; - } - - node = node.right; - } - } - assert (node !is null); - node.ch = value; -} - -HuffmanTreeNode* treeFromTable(ubyte[HuffmanCode] table) -{ - HuffmanTreeNode* root = new HuffmanTreeNode(null, null, 0, 0, false); - - foreach(i, v; table) - treeAddCode(root, i, v); - - return root; -} - -unittest -{ - string str = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; - - HuffmanTreeNode* tree; - ubyte[] enc = encodeHuffman(cast(ubyte[])str, tree); - - BitWriter bw; - packHuffmanTree(tree, &bw); - ubyte[] treeData = bw.getData(); - - BitReader br = BitReader(treeData); - HuffmanTreeNode* utree = unpackHuffmanTree(&br); - - ubyte[] dec = decodeHuffman(enc, utree); - auto decStr = cast(char[])dec; - - assert(decStr == str); -} - +*/ + +module dlib.image.io.huffman; + +private +{ + import std.stdio; + import std.algorithm; + import std.array; + import dlib.image.io.bitio; +} + +struct HuffmanTreeNode +{ + HuffmanTreeNode* parent; + HuffmanTreeNode* left; + HuffmanTreeNode* right; + ubyte ch; + uint freq; + bool blank = true; + + this( + HuffmanTreeNode* leftNode, + HuffmanTreeNode* rightNode, + ubyte symbol, + uint frequency, + bool isBlank) + { + parent = null; + left = leftNode; + right = rightNode; + + if (left !is null) + left.parent = &this; + if (right !is null) + right.parent = &this; + + ch = symbol; + freq = frequency; + blank = isBlank; + } + + bool isLeaf() + { + return (left is null && right is null); + } + + void getCodes(ref string[ubyte] table, string code = "") + { + if (isLeaf()) + { + table[ch] = code; + } + else + { + if (left !is null) + left.getCodes(table, code ~ '0'); + if (right !is null) + right.getCodes(table, code ~ '1'); + } + } + + void print(string indent = "") + { + writefln("%s<%s>%x", indent, freq, ch); + indent ~= " "; + if (left !is null) + left.print(indent); + if (right !is null) + right.print(indent); + } +} + +HuffmanTreeNode* buildHuffmanTree(ubyte[] data) +{ + // Count frequencies + uint[ubyte] freqs; + foreach(s; data) + { + if (s in freqs) + freqs[s] += 1; + else + freqs[s] = 1; + } + + // Sort in descending order + ubyte[] symbols = freqs.keys; + sort!((a, b) => freqs[a] > freqs[b])(symbols); + + // Create node list + auto nodeList = new HuffmanTreeNode*[symbols.length]; + foreach(i, s; symbols) + nodeList[i] = new HuffmanTreeNode(null, null, s, freqs[s], false); + + // Build tree + while (nodeList.length > 1) + { + // Pop two nodes with minimal frequencies + auto n1 = nodeList[$-1]; + auto n2 = nodeList[$-2]; + nodeList.popBack; + nodeList.popBack; + + // Insert a new parent node + uint fsum = n1.freq + n2.freq; + auto parent = new HuffmanTreeNode(n1, n2, 0, fsum, false); + nodeList ~= parent; + sort!((a, b) => a.freq > b.freq)(nodeList); + } + + auto root = nodeList[0]; + + return root; +} + +void packHuffmanTree(HuffmanTreeNode* node, BitWriter* bw) +{ + if (node.isLeaf) + { + bw.writeBit(true); + bw.writeByte(node.ch); + } + else + { + bw.writeBit(false); + packHuffmanTree(node.left, bw); + packHuffmanTree(node.right, bw); + } +} + +HuffmanTreeNode* unpackHuffmanTree(BitReader* br) +{ + if (!br.end) + { + bool bit = br.readBit(); + if (bit) + { + byte ch = br.readByte(); + return new HuffmanTreeNode(null, null, ch, 0, false); + } + else + { + HuffmanTreeNode* left = unpackHuffmanTree(br); + HuffmanTreeNode* right = unpackHuffmanTree(br); + return new HuffmanTreeNode(left, right, 0, 0, false); + } + } + else return null; +} + +ubyte[] encodeHuffman(ubyte[] data, out HuffmanTreeNode* tree) +{ + // Build Huffman tree + tree = buildHuffmanTree(data); + + // Generate binary codes + string[ubyte] huffTable; + tree.getCodes(huffTable); + + // Encode data + string bitStr; + foreach(s; data) + bitStr ~= huffTable[s]; + + // Pack bits to byte array + uint octetsLen = 0; + ubyte lastBits = 0; + if (bitStr.length == 8) + { + octetsLen = 1; + } + else if (bitStr.length > 8) + { + octetsLen = cast(uint)bitStr.length / 8; + lastBits = cast(ubyte)(bitStr.length % 8); + if (lastBits != 0) + octetsLen++; + } + else + { + octetsLen = 1; + lastBits = cast(ubyte)(bitStr.length); + } + + octetsLen++; + auto octets = new ubyte[octetsLen]; + octets[0] = lastBits; + + uint bitPos = 0; + uint bytePos = 1; + + foreach(bit; bitStr) + { + bool state; + if (bit == '0') + state = false; + else + state = true; + + octets[bytePos] = setBit(octets[bytePos], bitPos, state); + bitPos++; + + if (bitPos == 8) + { + bitPos = 0; + bytePos++; + } + } + + return octets; +} + +ubyte[] decodeHuffman(ubyte[] data, HuffmanTreeNode* tree) +{ + // Generate binary codes + string[ubyte] huffTable; + tree.getCodes(huffTable); + + //Unpack bits from array + ubyte[] result; + bool appendNext = true; + string code = ""; + ubyte lastBits = data[0]; + foreach(i, b; data[1..$]) + { + uint len; + if ((lastBits != 0) && (i == data.length-1)) + len = lastBits; + else + len = 8; + + foreach(bp; 0..len) + { + char bitChr = getBit(b, bp)? '1':'0'; + if (appendNext) + { + code ~= bitChr; + foreach(key, val; huffTable) + { + if (code == val) + { + result ~= key; + appendNext = false; + break; + } + } + } + else + { + code = ""; + code ~= bitChr; + appendNext = true; + } + } + } + + return result; +} + +struct HuffmanCode +{ + ushort bits; + uint length; + + string toString() + { + return bitString(bits, length); + } +} + +void treeAddCode(HuffmanTreeNode* root, HuffmanCode code, ubyte value) +{ + HuffmanTreeNode* node = root; + foreach(bit; code.toString) + { + if (bit == '0') + { + if (node.left is null) + { + node.left = new HuffmanTreeNode(null, null, 0, 0, false); + node.left.parent = node; + } + + node = node.left; + } + else if (bit == '1') + { + if (node.right is null) + { + node.right = new HuffmanTreeNode(null, null, 0, 0, false); + node.right.parent = node; + } + + node = node.right; + } + } + assert (node !is null); + node.ch = value; +} + +HuffmanTreeNode* treeFromTable(ubyte[HuffmanCode] table) +{ + HuffmanTreeNode* root = new HuffmanTreeNode(null, null, 0, 0, false); + + foreach(i, v; table) + treeAddCode(root, i, v); + + return root; +} + +unittest +{ + string str = "Lorem ipsum dolor sit amet, consectetur adipisicing elit"; + + HuffmanTreeNode* tree; + ubyte[] enc = encodeHuffman(cast(ubyte[])str, tree); + + BitWriter bw; + packHuffmanTree(tree, &bw); + ubyte[] treeData = bw.getData(); + + BitReader br = BitReader(treeData); + HuffmanTreeNode* utree = unpackHuffmanTree(&br); + + ubyte[] dec = decodeHuffman(enc, utree); + auto decStr = cast(char[])dec; + + assert(decStr == str); +} + diff --git a/dlib/math/matrix.d b/dlib/math/matrix.d index ad961a41..907cbead 100644 --- a/dlib/math/matrix.d +++ b/dlib/math/matrix.d @@ -910,7 +910,7 @@ string matrixToStr(T, size_t N)(Matrix!(T, N) m) { num = format("% s", to!long(integ)); if (num.length > width) - width = num.length; + width = cast(uint)num.length; } else {