├── .gitignore ├── Makefile ├── README.md └── src ├── decoder.cpp ├── encoder.cpp └── jpg.h /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | *.jpg 3 | *.jpeg 4 | *.bmp 5 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: 2 | @mkdir -p bin 3 | g++ --std=c++14 -O3 -o bin/encoder src/encoder.cpp 4 | g++ --std=c++14 -O3 -o bin/decoder src/decoder.cpp 5 | 6 | clean: 7 | rm -f bin/encoder bin/decoder 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # jed 2 | 3 | A C++ JPG Encoder/Decoder 4 | 5 | jed encodes uncompressed BMPs and outputs them as baseline JPGs. 6 | 7 | jed decodes all standard JPGs (baseline, progressive, subsampled) and outputs them in BMP format. 8 | 9 | This project was created for the video series, [**Everything You Need to Know About JPEG**][yt]. 10 | 11 | [yt]: https://www.youtube.com/playlist?list=PLpsTn9TA_Q8VMDyOPrDKmSJYt1DLgDZU4 12 | -------------------------------------------------------------------------------- /src/decoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include "jpg.h" 5 | 6 | // helper class to read bits from a file 7 | class BitReader { 8 | private: 9 | byte nextByte = 0; 10 | byte nextBit = 0; 11 | std::ifstream inFile; 12 | 13 | public: 14 | BitReader(const std::string& filename) { 15 | inFile.open(filename, std::ios::in | std::ios::binary); 16 | } 17 | 18 | ~BitReader() { 19 | if (inFile.is_open()) { 20 | inFile.close(); 21 | } 22 | } 23 | 24 | bool hasBits() { 25 | return !!inFile; 26 | } 27 | 28 | byte readByte() { 29 | nextBit = 0; 30 | return inFile.get(); 31 | } 32 | 33 | uint readWord() { 34 | nextBit = 0; 35 | return (inFile.get() << 8) + inFile.get(); 36 | } 37 | 38 | // read one bit (0 or 1) or return -1 if all bits have already been read 39 | uint readBit() { 40 | if (nextBit == 0) { 41 | if (!hasBits()) { 42 | return -1; 43 | } 44 | nextByte = inFile.get(); 45 | while (nextByte == 0xFF) { 46 | if (!hasBits()) { 47 | return -1; 48 | } 49 | byte marker = inFile.peek(); 50 | // ignore multiple 0xFF's in a row 51 | while (marker == 0xFF) { 52 | inFile.get(); 53 | if (!hasBits()) { 54 | return -1; 55 | } 56 | marker = inFile.peek(); 57 | } 58 | // literal 0xFF's are encoded in the bitstream as 0xFF00 59 | if (marker == 0x00) { 60 | inFile.get(); 61 | break; 62 | } 63 | // restart marker 64 | else if (marker >= RST0 && marker <= RST7) { 65 | inFile.get(); 66 | nextByte = inFile.get(); 67 | } 68 | else { 69 | std::cout << "Error - Invalid marker: 0x" << std::hex << (uint)marker << std::dec << '\n'; 70 | return -1; 71 | } 72 | } 73 | } 74 | uint bit = (nextByte >> (7 - nextBit)) & 1; 75 | nextBit = (nextBit + 1) % 8; 76 | return bit; 77 | } 78 | 79 | // read a variable number of bits 80 | // first read bit is most significant bit 81 | // return -1 if at any point all bits have already been read 82 | uint readBits(const uint length) { 83 | uint bits = 0; 84 | for (uint i = 0; i < length; ++i) { 85 | uint bit = readBit(); 86 | if (bit == -1) { 87 | bits = -1; 88 | break; 89 | } 90 | bits = (bits << 1) | bit; 91 | } 92 | return bits; 93 | } 94 | 95 | // advance to the 0th bit of the next byte 96 | void align() { 97 | nextBit = 0; 98 | } 99 | }; 100 | 101 | // SOF specifies frame type, dimensions, and number of color components 102 | void readStartOfFrame(BitReader& bitReader, JPGImage* const image) { 103 | std::cout << "Reading SOF Marker\n"; 104 | if (image->numComponents != 0) { 105 | std::cout << "Error - Multiple SOFs detected\n"; 106 | image->valid = false; 107 | return; 108 | } 109 | 110 | uint length = bitReader.readWord(); 111 | 112 | byte precision = bitReader.readByte(); 113 | if (precision != 8) { 114 | std::cout << "Error - Invalid precision: " << (uint)precision << '\n'; 115 | image->valid = false; 116 | return; 117 | } 118 | 119 | image->height = bitReader.readWord(); 120 | image->width = bitReader.readWord(); 121 | if (image->height == 0 || image->width == 0) { 122 | std::cout << "Error - Invalid dimensions\n"; 123 | image->valid = false; 124 | return; 125 | } 126 | image->blockHeight = (image->height + 7) / 8; 127 | image->blockWidth = (image->width + 7) / 8; 128 | image->blockHeightReal = image->blockHeight; 129 | image->blockWidthReal = image->blockWidth; 130 | 131 | image->numComponents = bitReader.readByte(); 132 | if (image->numComponents == 4) { 133 | std::cout << "Error - CMYK color mode not supported\n"; 134 | image->valid = false; 135 | return; 136 | } 137 | if (image->numComponents != 1 && image->numComponents != 3) { 138 | std::cout << "Error - " << (uint)image->numComponents << " color components given (1 or 3 required)\n"; 139 | image->valid = false; 140 | return; 141 | } 142 | for (uint i = 0; i < image->numComponents; ++i) { 143 | byte componentID = bitReader.readByte(); 144 | // component IDs are usually 1, 2, 3 but rarely can be seen as 0, 1, 2 145 | // always force them into 1, 2, 3 for consistency 146 | if (componentID == 0 && i == 0) { 147 | image->zeroBased = true; 148 | } 149 | if (image->zeroBased) { 150 | componentID += 1; 151 | } 152 | if (componentID == 0 || componentID > image->numComponents) { 153 | std::cout << "Error - Invalid component ID: " << (uint)componentID << '\n'; 154 | image->valid = false; 155 | return; 156 | } 157 | ColorComponent& component = image->colorComponents[componentID - 1]; 158 | if (component.usedInFrame) { 159 | std::cout << "Error - Duplicate color component ID: " << (uint)componentID << '\n'; 160 | image->valid = false; 161 | return; 162 | } 163 | component.usedInFrame = true; 164 | 165 | byte samplingFactor = bitReader.readByte(); 166 | component.horizontalSamplingFactor = samplingFactor >> 4; 167 | component.verticalSamplingFactor = samplingFactor & 0x0F; 168 | if (componentID == 1) { 169 | if ((component.horizontalSamplingFactor != 1 && component.horizontalSamplingFactor != 2) || 170 | (component.verticalSamplingFactor != 1 && component.verticalSamplingFactor != 2)) { 171 | std::cout << "Error - Sampling factors not supported\n"; 172 | image->valid = false; 173 | return; 174 | } 175 | if (component.horizontalSamplingFactor == 2 && image->blockWidth % 2 == 1) { 176 | image->blockWidthReal += 1; 177 | } 178 | if (component.verticalSamplingFactor == 2 && image->blockHeight % 2 == 1) { 179 | image->blockHeightReal += 1; 180 | } 181 | image->horizontalSamplingFactor = component.horizontalSamplingFactor; 182 | image->verticalSamplingFactor = component.verticalSamplingFactor; 183 | } 184 | else { 185 | if (component.horizontalSamplingFactor != 1 || component.verticalSamplingFactor != 1) { 186 | std::cout << "Error - Sampling factors not supported\n"; 187 | image->valid = false; 188 | return; 189 | } 190 | } 191 | 192 | component.quantizationTableID = bitReader.readByte(); 193 | if (component.quantizationTableID > 3) { 194 | std::cout << "Error - Invalid quantization table ID: " << (uint)component.quantizationTableID << '\n'; 195 | image->valid = false; 196 | return; 197 | } 198 | } 199 | 200 | if (length - 8 - (3 * image->numComponents) != 0) { 201 | std::cout << "Error - SOF invalid\n"; 202 | image->valid = false; 203 | return; 204 | } 205 | } 206 | 207 | // DQT contains one or more quantization tables 208 | void readQuantizationTable(BitReader& bitReader, JPGImage* const image) { 209 | std::cout << "Reading DQT Marker\n"; 210 | int length = bitReader.readWord(); 211 | length -= 2; 212 | 213 | while (length > 0) { 214 | byte tableInfo = bitReader.readByte(); 215 | length -= 1; 216 | byte tableID = tableInfo & 0x0F; 217 | 218 | if (tableID > 3) { 219 | std::cout << "Error - Invalid quantization table ID: " << (uint)tableID << '\n'; 220 | image->valid = false; 221 | return; 222 | } 223 | QuantizationTable& qTable = image->quantizationTables[tableID]; 224 | qTable.set = true; 225 | 226 | if (tableInfo >> 4 != 0) { 227 | for (uint i = 0; i < 64; ++i) { 228 | qTable.table[zigZagMap[i]] = bitReader.readWord(); 229 | } 230 | length -= 128; 231 | } 232 | else { 233 | for (uint i = 0; i < 64; ++i) { 234 | qTable.table[zigZagMap[i]] = bitReader.readByte(); 235 | } 236 | length -= 64; 237 | } 238 | } 239 | 240 | if (length != 0) { 241 | std::cout << "Error - DQT invalid\n"; 242 | image->valid = false; 243 | return; 244 | } 245 | } 246 | 247 | // generate all Huffman codes based on symbols from a Huffman table 248 | void generateCodes(HuffmanTable& hTable) { 249 | uint code = 0; 250 | for (uint i = 0; i < 16; ++i) { 251 | for (uint j = hTable.offsets[i]; j < hTable.offsets[i + 1]; ++j) { 252 | hTable.codes[j] = code; 253 | code += 1; 254 | } 255 | code <<= 1; 256 | } 257 | } 258 | 259 | // DHT contains one or more Huffman tables 260 | void readHuffmanTable(BitReader& bitReader, JPGImage* const image) { 261 | std::cout << "Reading DHT Marker\n"; 262 | int length = bitReader.readWord(); 263 | length -= 2; 264 | 265 | while (length > 0) { 266 | byte tableInfo = bitReader.readByte(); 267 | byte tableID = tableInfo & 0x0F; 268 | bool acTable = tableInfo >> 4; 269 | 270 | if (tableID > 3) { 271 | std::cout << "Error - Invalid Huffman table ID: " << (uint)tableID << '\n'; 272 | image->valid = false; 273 | return; 274 | } 275 | 276 | HuffmanTable& hTable = (acTable) ? 277 | (image->huffmanACTables[tableID]) : 278 | (image->huffmanDCTables[tableID]); 279 | hTable.set = true; 280 | 281 | hTable.offsets[0] = 0; 282 | uint allSymbols = 0; 283 | for (uint i = 1; i <= 16; ++i) { 284 | allSymbols += bitReader.readByte(); 285 | hTable.offsets[i] = allSymbols; 286 | } 287 | if (allSymbols > 176) { 288 | std::cout << "Error - Too many symbols in Huffman table: " << allSymbols << '\n'; 289 | image->valid = false; 290 | return; 291 | } 292 | 293 | for (uint i = 0; i < allSymbols; ++i) { 294 | hTable.symbols[i] = bitReader.readByte(); 295 | } 296 | 297 | generateCodes(hTable); 298 | 299 | length -= 17 + allSymbols; 300 | } 301 | 302 | if (length != 0) { 303 | std::cout << "Error - DHT invalid\n"; 304 | image->valid = false; 305 | return; 306 | } 307 | } 308 | 309 | // SOS contains color component info for the next scan 310 | void readStartOfScan(BitReader& bitReader, JPGImage* const image) { 311 | std::cout << "Reading SOS Marker\n"; 312 | if (image->numComponents == 0) { 313 | std::cout << "Error - SOS detected before SOF\n"; 314 | image->valid = false; 315 | return; 316 | } 317 | 318 | uint length = bitReader.readWord(); 319 | 320 | for (uint i = 0; i < image->numComponents; ++i) { 321 | image->colorComponents[i].usedInScan = false; 322 | } 323 | 324 | // the number of components in the next scan might not be all 325 | // components in the image 326 | image->componentsInScan = bitReader.readByte(); 327 | if (image->componentsInScan == 0) { 328 | std::cout << "Error - Scan must include at least 1 component\n"; 329 | image->valid = false; 330 | return; 331 | } 332 | for (uint i = 0; i < image->componentsInScan; ++i) { 333 | byte componentID = bitReader.readByte(); 334 | // component IDs are usually 1, 2, 3 but rarely can be seen as 0, 1, 2 335 | if (image->zeroBased) { 336 | componentID += 1; 337 | } 338 | if (componentID == 0 || componentID > image->numComponents) { 339 | std::cout << "Error - Invalid color component ID: " << (uint)componentID << '\n'; 340 | image->valid = false; 341 | return; 342 | } 343 | ColorComponent& component = image->colorComponents[componentID - 1]; 344 | if (!component.usedInFrame) { 345 | std::cout << "Error - Invalid color component ID: " << (uint)componentID << '\n'; 346 | image->valid = false; 347 | return; 348 | } 349 | if (component.usedInScan) { 350 | std::cout << "Error - Duplicate color component ID: " << (uint)componentID << '\n'; 351 | image->valid = false; 352 | return; 353 | } 354 | component.usedInScan = true; 355 | 356 | byte huffmanTableIDs = bitReader.readByte(); 357 | component.huffmanDCTableID = huffmanTableIDs >> 4; 358 | component.huffmanACTableID = huffmanTableIDs & 0x0F; 359 | if (component.huffmanDCTableID > 3) { 360 | std::cout << "Error - Invalid Huffman DC table ID: " << (uint)component.huffmanDCTableID << '\n'; 361 | image->valid = false; 362 | return; 363 | } 364 | if (component.huffmanACTableID > 3) { 365 | std::cout << "Error - Invalid Huffman AC table ID: " << (uint)component.huffmanACTableID << '\n'; 366 | image->valid = false; 367 | return; 368 | } 369 | } 370 | 371 | image->startOfSelection = bitReader.readByte(); 372 | image->endOfSelection = bitReader.readByte(); 373 | byte successiveApproximation = bitReader.readByte(); 374 | image->successiveApproximationHigh = successiveApproximation >> 4; 375 | image->successiveApproximationLow = successiveApproximation & 0x0F; 376 | 377 | if (image->frameType == SOF0) { 378 | // Baseline JPGs don't use spectral selection or successive approximtion 379 | if (image->startOfSelection != 0 || image->endOfSelection != 63) { 380 | std::cout << "Error - Invalid spectral selection\n"; 381 | image->valid = false; 382 | return; 383 | } 384 | if (image->successiveApproximationHigh != 0 || image->successiveApproximationLow != 0) { 385 | std::cout << "Error - Invalid successive approximation\n"; 386 | image->valid = false; 387 | return; 388 | } 389 | } 390 | else if (image->frameType == SOF2) { 391 | if (image->startOfSelection > image->endOfSelection) { 392 | std::cout << "Error - Invalid spectral selection (start greater than end)\n"; 393 | image->valid = false; 394 | return; 395 | } 396 | if (image->endOfSelection > 63) { 397 | std::cout << "Error - Invalid spectral selection (end greater than 63)\n"; 398 | image->valid = false; 399 | return; 400 | } 401 | if (image->startOfSelection == 0 && image->endOfSelection != 0) { 402 | std::cout << "Error - Invalid spectral selection (contains DC and AC)\n"; 403 | image->valid = false; 404 | return; 405 | } 406 | if (image->startOfSelection != 0 && image->componentsInScan != 1) { 407 | std::cout << "Error - Invalid spectral selection (AC scan contains multiple components)\n"; 408 | image->valid = false; 409 | return; 410 | } 411 | if (image->successiveApproximationHigh != 0 && 412 | image->successiveApproximationLow != image->successiveApproximationHigh - 1) { 413 | std::cout << "Error - Invalid successive approximation\n"; 414 | image->valid = false; 415 | return; 416 | } 417 | } 418 | 419 | for (uint i = 0; i < image->numComponents; ++i) { 420 | const ColorComponent& component = image->colorComponents[i]; 421 | if (image->colorComponents[i].usedInScan) { 422 | if (image->quantizationTables[component.quantizationTableID].set == false) { 423 | std::cout << "Error - Color component using uninitialized quantization table\n"; 424 | image->valid = false; 425 | return; 426 | } 427 | if (image->startOfSelection == 0) { 428 | if (image->huffmanDCTables[component.huffmanDCTableID].set == false) { 429 | std::cout << "Error - Color component using uninitialized Huffman DC table\n"; 430 | image->valid = false; 431 | return; 432 | } 433 | } 434 | if (image->endOfSelection > 0) { 435 | if (image->huffmanACTables[component.huffmanACTableID].set == false) { 436 | std::cout << "Error - Color component using uninitialized Huffman AC table\n"; 437 | image->valid = false; 438 | return; 439 | } 440 | } 441 | } 442 | } 443 | 444 | if (length - 6 - (2 * image->componentsInScan) != 0) { 445 | std::cout << "Error - SOS invalid\n"; 446 | image->valid = false; 447 | return; 448 | } 449 | } 450 | 451 | // restart interval is needed to stay synchronized during data scans 452 | void readRestartInterval(BitReader& bitReader, JPGImage* const image) { 453 | std::cout << "Reading DRI Marker\n"; 454 | uint length = bitReader.readWord(); 455 | 456 | image->restartInterval = bitReader.readWord(); 457 | if (length - 4 != 0) { 458 | std::cout << "Error - DRI invalid\n"; 459 | image->valid = false; 460 | return; 461 | } 462 | } 463 | 464 | // APPNs simply get skipped based on length 465 | void readAPPN(BitReader& bitReader, JPGImage* const image) { 466 | std::cout << "Reading APPN Marker\n"; 467 | uint length = bitReader.readWord(); 468 | if (length < 2) { 469 | std::cout << "Error - APPN invalid\n"; 470 | image->valid = false; 471 | return; 472 | } 473 | 474 | for (uint i = 0; i < length - 2; ++i) { 475 | bitReader.readByte(); 476 | } 477 | } 478 | 479 | // comments simply get skipped based on length 480 | void readComment(BitReader& bitReader, JPGImage* const image) { 481 | std::cout << "Reading COM Marker\n"; 482 | uint length = bitReader.readWord(); 483 | if (length < 2) { 484 | std::cout << "Error - COM invalid\n"; 485 | image->valid = false; 486 | return; 487 | } 488 | 489 | for (uint i = 0; i < length - 2; ++i) { 490 | bitReader.readByte(); 491 | } 492 | } 493 | 494 | // print all info extracted from the JPG file 495 | void printFrameInfo(const JPGImage* const image) { 496 | if (image == nullptr) return; 497 | std::cout << "SOF=============\n"; 498 | std::cout << "Frame Type: 0x" << std::hex << (uint)image->frameType << std::dec << '\n'; 499 | std::cout << "Height: " << image->height << '\n'; 500 | std::cout << "Width: " << image->width << '\n'; 501 | std::cout << "Color Components:\n"; 502 | for (uint i = 0; i < image->numComponents; ++i) { 503 | if (image->colorComponents[i].usedInFrame) { 504 | std::cout << "Component ID: " << (i + 1) << '\n'; 505 | std::cout << "Horizontal Sampling Factor: " << (uint)image->colorComponents[i].horizontalSamplingFactor << '\n'; 506 | std::cout << "Vertical Sampling Factor: " << (uint)image->colorComponents[i].verticalSamplingFactor << '\n'; 507 | std::cout << "Quantization Table ID: " << (uint)image->colorComponents[i].quantizationTableID << '\n'; 508 | } 509 | } 510 | std::cout << "DQT=============\n"; 511 | for (uint i = 0; i < 4; ++i) { 512 | if (image->quantizationTables[i].set) { 513 | std::cout << "Table ID: " << i << '\n'; 514 | std::cout << "Table Data:"; 515 | for (uint j = 0; j < 64; ++j) { 516 | if (j % 8 == 0) { 517 | std::cout << '\n'; 518 | } 519 | std::cout << image->quantizationTables[i].table[j] << ' '; 520 | } 521 | std::cout << '\n'; 522 | } 523 | } 524 | } 525 | 526 | // print info for the next scan 527 | void printScanInfo(const JPGImage* const image) { 528 | if (image == nullptr) return; 529 | std::cout << "SOS=============\n"; 530 | std::cout << "Start of Selection: " << (uint)image->startOfSelection << '\n'; 531 | std::cout << "End of Selection: " << (uint)image->endOfSelection << '\n'; 532 | std::cout << "Successive Approximation High: " << (uint)image->successiveApproximationHigh << '\n'; 533 | std::cout << "Successive Approximation Low: " << (uint)image->successiveApproximationLow << '\n'; 534 | std::cout << "Color Components:\n"; 535 | for (uint i = 0; i < image->numComponents; ++i) { 536 | if (image->colorComponents[i].usedInScan) { 537 | std::cout << "Component ID: " << (i + 1) << '\n'; 538 | std::cout << "Huffman DC Table ID: " << (uint)image->colorComponents[i].huffmanDCTableID << '\n'; 539 | std::cout << "Huffman AC Table ID: " << (uint)image->colorComponents[i].huffmanACTableID << '\n'; 540 | } 541 | } 542 | std::cout << "DHT=============\n"; 543 | std::cout << "DC Tables:\n"; 544 | for (uint i = 0; i < 4; ++i) { 545 | if (image->huffmanDCTables[i].set) { 546 | std::cout << "Table ID: " << i << '\n'; 547 | std::cout << "Symbols:\n"; 548 | for (uint j = 0; j < 16; ++j) { 549 | std::cout << (j + 1) << ": "; 550 | for (uint k = image->huffmanDCTables[i].offsets[j]; k < image->huffmanDCTables[i].offsets[j + 1]; ++k) { 551 | std::cout << std::hex << (uint)image->huffmanDCTables[i].symbols[k] << std::dec << ' '; 552 | } 553 | std::cout << '\n'; 554 | } 555 | } 556 | } 557 | std::cout << "AC Tables:\n"; 558 | for (uint i = 0; i < 4; ++i) { 559 | if (image->huffmanACTables[i].set) { 560 | std::cout << "Table ID: " << i << '\n'; 561 | std::cout << "Symbols:\n"; 562 | for (uint j = 0; j < 16; ++j) { 563 | std::cout << (j + 1) << ": "; 564 | for (uint k = image->huffmanACTables[i].offsets[j]; k < image->huffmanACTables[i].offsets[j + 1]; ++k) { 565 | std::cout << std::hex << (uint)image->huffmanACTables[i].symbols[k] << std::dec << ' '; 566 | } 567 | std::cout << '\n'; 568 | } 569 | } 570 | } 571 | std::cout << "DRI=============\n"; 572 | std::cout << "Restart Interval: " << image->restartInterval << '\n'; 573 | } 574 | 575 | void readFrameHeader(BitReader& bitReader, JPGImage* const image) { 576 | // first two bytes must be 0xFF, SOI 577 | byte last = bitReader.readByte(); 578 | byte current = bitReader.readByte(); 579 | if (last != 0xFF || current != SOI) { 580 | std::cout << "Error - SOI invalid\n"; 581 | image->valid = false; 582 | return; 583 | } 584 | last = bitReader.readByte(); 585 | current = bitReader.readByte(); 586 | 587 | // read markers until first scan 588 | while (image->valid) { 589 | if (!bitReader.hasBits()) { 590 | std::cout << "Error - File ended prematurely\n"; 591 | image->valid = false; 592 | return; 593 | } 594 | if (last != 0xFF) { 595 | std::cout << "Error - Expected a marker\n"; 596 | image->valid = false; 597 | return; 598 | } 599 | 600 | if (current == SOF0) { 601 | image->frameType = SOF0; 602 | readStartOfFrame(bitReader, image); 603 | } 604 | else if (current == SOF2) { 605 | image->frameType = SOF2; 606 | readStartOfFrame(bitReader, image); 607 | } 608 | else if (current == DQT) { 609 | readQuantizationTable(bitReader, image); 610 | } 611 | else if (current == DHT) { 612 | readHuffmanTable(bitReader, image); 613 | } 614 | else if (current == SOS) { 615 | // break from while loop at SOS 616 | break; 617 | } 618 | else if (current == DRI) { 619 | readRestartInterval(bitReader, image); 620 | } 621 | else if (current >= APP0 && current <= APP15) { 622 | readAPPN(bitReader, image); 623 | } 624 | else if (current == COM) { 625 | readComment(bitReader, image); 626 | } 627 | // unused markers that can be skipped 628 | else if ((current >= JPG0 && current <= JPG13) || 629 | current == DNL || 630 | current == DHP || 631 | current == EXP) { 632 | readComment(bitReader, image); 633 | } 634 | else if (current == TEM) { 635 | // TEM has no size 636 | } 637 | // any number of 0xFF in a row is allowed and should be ignored 638 | else if (current == 0xFF) { 639 | current = bitReader.readByte(); 640 | continue; 641 | } 642 | 643 | else if (current == SOI) { 644 | std::cout << "Error - Embedded JPGs not supported\n"; 645 | image->valid = false; 646 | return; 647 | } 648 | else if (current == EOI) { 649 | std::cout << "Error - EOI detected before SOS\n"; 650 | image->valid = false; 651 | return; 652 | } 653 | else if (current == DAC) { 654 | std::cout << "Error - Arithmetic Coding mode not supported\n"; 655 | image->valid = false; 656 | return; 657 | } 658 | else if (current >= SOF0 && current <= SOF15) { 659 | std::cout << "Error - SOF marker not supported: 0x" << std::hex << (uint)current << std::dec << '\n'; 660 | image->valid = false; 661 | return; 662 | } 663 | else if (current >= RST0 && current <= RST7) { 664 | std::cout << "Error - RSTN detected before SOS\n"; 665 | image->valid = false; 666 | return; 667 | } 668 | else { 669 | std::cout << "Error - Unknown marker: 0x" << std::hex << (uint)current << std::dec << '\n'; 670 | image->valid = false; 671 | return; 672 | } 673 | last = bitReader.readByte(); 674 | current = bitReader.readByte(); 675 | } 676 | } 677 | 678 | void decodeHuffmanData(BitReader& bitReader, JPGImage* const image); 679 | 680 | void readScans(BitReader& bitReader, JPGImage* const image) { 681 | // decode first scan 682 | readStartOfScan(bitReader, image); 683 | if (!image->valid) { 684 | return; 685 | } 686 | printScanInfo(image); 687 | decodeHuffmanData(bitReader, image); 688 | 689 | byte last = bitReader.readByte(); 690 | byte current = bitReader.readByte(); 691 | 692 | // decode additional scans, if any 693 | while (image->valid) { 694 | if (!bitReader.hasBits()) { 695 | std::cout << "Error - File ended prematurely\n"; 696 | image->valid = false; 697 | return; 698 | } 699 | if (last != 0xFF) { 700 | std::cout << "Error - Expected a marker\n"; 701 | image->valid = false; 702 | return; 703 | } 704 | 705 | // end of image 706 | if (current == EOI) { 707 | break; 708 | } 709 | // huffman tables (progressive only) 710 | else if (current == DHT && image->frameType == SOF2) { 711 | readHuffmanTable(bitReader, image); 712 | } 713 | // additional scans (progressive only) 714 | else if (current == SOS && image->frameType == SOF2) { 715 | readStartOfScan(bitReader, image); 716 | if (!image->valid) { 717 | return; 718 | } 719 | printScanInfo(image); 720 | decodeHuffmanData(bitReader, image); 721 | } 722 | // new restart interval (progressive only) 723 | else if (current == DRI && image->frameType == SOF2) { 724 | readRestartInterval(bitReader, image); 725 | } 726 | // restart marker, perhaps from the very end of previous scan 727 | else if (current >= RST0 && current <= RST7) { 728 | // RSTN has no size 729 | } 730 | // ignore multiple 0xFF's in a row 731 | else if (current == 0xFF) { 732 | current = bitReader.readByte(); 733 | continue; 734 | } 735 | else { 736 | std::cout << "Error - Invalid marker: 0x" << std::hex << (uint)current << std::dec << '\n'; 737 | image->valid = false; 738 | return; 739 | } 740 | last = bitReader.readByte(); 741 | current = bitReader.readByte(); 742 | } 743 | } 744 | 745 | JPGImage* readJPG(const std::string& filename) { 746 | // open file 747 | std::cout << "Reading " << filename << "...\n"; 748 | BitReader bitReader(filename); 749 | if (!bitReader.hasBits()) { 750 | std::cout << "Error - Error opening input file\n"; 751 | return nullptr; 752 | } 753 | 754 | JPGImage* image = new (std::nothrow) JPGImage; 755 | if (image == nullptr) { 756 | std::cout << "Error - Memory error\n"; 757 | return nullptr; 758 | } 759 | 760 | readFrameHeader(bitReader, image); 761 | 762 | if (!image->valid) { 763 | return image; 764 | } 765 | 766 | printFrameInfo(image); 767 | 768 | image->blocks = new (std::nothrow) Block[image->blockHeightReal * image->blockWidthReal]; 769 | if (image->blocks == nullptr) { 770 | std::cout << "Error - Memory error\n"; 771 | image->valid = false; 772 | return image; 773 | } 774 | 775 | readScans(bitReader, image); 776 | 777 | return image; 778 | } 779 | 780 | // return the symbol from the Huffman table that corresponds to 781 | // the next Huffman code read from the BitReader 782 | byte getNextSymbol(BitReader& bitReader, const HuffmanTable& hTable) { 783 | uint currentCode = 0; 784 | for (uint i = 0; i < 16; ++i) { 785 | int bit = bitReader.readBit(); 786 | if (bit == -1) { 787 | return -1; 788 | } 789 | currentCode = (currentCode << 1) | bit; 790 | for (uint j = hTable.offsets[i]; j < hTable.offsets[i + 1]; ++j) { 791 | if (currentCode == hTable.codes[j]) { 792 | return hTable.symbols[j]; 793 | } 794 | } 795 | } 796 | return -1; 797 | } 798 | 799 | // fill the coefficients of a block component based on Huffman codes 800 | // read from the BitReader 801 | bool decodeBlockComponent( 802 | const JPGImage* const image, 803 | BitReader& bitReader, 804 | int* const component, 805 | int& previousDC, 806 | uint& skips, 807 | const HuffmanTable& dcTable, 808 | const HuffmanTable& acTable 809 | ) { 810 | if (image->frameType == SOF0) { 811 | // get the DC value for this block component 812 | byte length = getNextSymbol(bitReader, dcTable); 813 | if (length == (byte)-1) { 814 | std::cout << "Error - Invalid DC value\n"; 815 | return false; 816 | } 817 | if (length > 11) { 818 | std::cout << "Error - DC coefficient length greater than 11\n"; 819 | return false; 820 | } 821 | 822 | int coeff = bitReader.readBits(length); 823 | if (coeff == -1) { 824 | std::cout << "Error - Invalid DC value\n"; 825 | return false; 826 | } 827 | if (length != 0 && coeff < (1 << (length - 1))) { 828 | coeff -= (1 << length) - 1; 829 | } 830 | component[0] = coeff + previousDC; 831 | previousDC = component[0]; 832 | 833 | // get the AC values for this block component 834 | for (uint i = 1; i < 64; ++i) { 835 | byte symbol = getNextSymbol(bitReader, acTable); 836 | if (symbol == (byte)-1) { 837 | std::cout << "Error - Invalid AC value\n"; 838 | return false; 839 | } 840 | 841 | // symbol 0x00 means fill remainder of component with 0 842 | if (symbol == 0x00) { 843 | return true; 844 | } 845 | 846 | // otherwise, read next component coefficient 847 | byte numZeroes = symbol >> 4; 848 | byte coeffLength = symbol & 0x0F; 849 | coeff = 0; 850 | 851 | if (i + numZeroes >= 64) { 852 | std::cout << "Error - Zero run-length exceeded block component\n"; 853 | return false; 854 | } 855 | i += numZeroes; 856 | 857 | if (coeffLength > 10) { 858 | std::cout << "Error - AC coefficient length greater than 10\n"; 859 | return false; 860 | } 861 | coeff = bitReader.readBits(coeffLength); 862 | if (coeff == -1) { 863 | std::cout << "Error - Invalid AC value\n"; 864 | return false; 865 | } 866 | if (coeff < (1 << (coeffLength - 1))) { 867 | coeff -= (1 << coeffLength) - 1; 868 | } 869 | component[zigZagMap[i]] = coeff; 870 | } 871 | return true; 872 | } 873 | else { // image->frameType == SOF2 874 | if (image->startOfSelection == 0 && image->successiveApproximationHigh == 0) { 875 | // DC first visit 876 | byte length = getNextSymbol(bitReader, dcTable); 877 | if (length == (byte)-1) { 878 | std::cout << "Error - Invalid DC value\n"; 879 | return false; 880 | } 881 | if (length > 11) { 882 | std::cout << "Error - DC coefficient length greater than 11\n"; 883 | return false; 884 | } 885 | 886 | int coeff = bitReader.readBits(length); 887 | if (coeff == -1) { 888 | std::cout << "Error - Invalid DC value\n"; 889 | return false; 890 | } 891 | if (length != 0 && coeff < (1 << (length - 1))) { 892 | coeff -= (1 << length) - 1; 893 | } 894 | coeff += previousDC; 895 | previousDC = coeff; 896 | component[0] = coeff << image->successiveApproximationLow; 897 | return true; 898 | } 899 | else if (image->startOfSelection == 0 && image->successiveApproximationHigh != 0) { 900 | // DC refinement 901 | int bit = bitReader.readBit(); 902 | if (bit == -1) { 903 | std::cout << "Error - Invalid DC value\n"; 904 | return false; 905 | } 906 | component[0] |= bit << image->successiveApproximationLow; 907 | return true; 908 | } 909 | else if (image->startOfSelection != 0 && image->successiveApproximationHigh == 0) { 910 | // AC first visit 911 | if (skips > 0) { 912 | skips -= 1; 913 | return true; 914 | } 915 | for (uint i = image->startOfSelection; i <= image->endOfSelection; ++i) { 916 | byte symbol = getNextSymbol(bitReader, acTable); 917 | if (symbol == (byte)-1) { 918 | std::cout << "Error - Invalid AC value\n"; 919 | return false; 920 | } 921 | 922 | byte numZeroes = symbol >> 4; 923 | byte coeffLength = symbol & 0x0F; 924 | 925 | if (coeffLength != 0) { 926 | if (i + numZeroes > image->endOfSelection) { 927 | std::cout << "Error - Zero run-length exceeded spectral selection\n"; 928 | return false; 929 | } 930 | for (uint j = 0; j < numZeroes; ++j, ++i) { 931 | component[zigZagMap[i]] = 0; 932 | } 933 | if (coeffLength > 10) { 934 | std::cout << "Error - AC coefficient length greater than 10\n"; 935 | return false; 936 | } 937 | 938 | int coeff = bitReader.readBits(coeffLength); 939 | if (coeff == -1) { 940 | std::cout << "Error - Invalid AC value\n"; 941 | return false; 942 | } 943 | if (coeff < (1 << (coeffLength - 1))) { 944 | coeff -= (1 << coeffLength) - 1; 945 | } 946 | component[zigZagMap[i]] = coeff << image->successiveApproximationLow; 947 | } 948 | else { 949 | if (numZeroes == 15) { 950 | if (i + numZeroes > image->endOfSelection) { 951 | std::cout << "Error - Zero run-length exceeded spectral selection\n"; 952 | return false; 953 | } 954 | for (uint j = 0; j < numZeroes; ++j, ++i) { 955 | component[zigZagMap[i]] = 0; 956 | } 957 | } 958 | else { 959 | skips = (1 << numZeroes) - 1; 960 | uint extraSkips = bitReader.readBits(numZeroes); 961 | if (extraSkips == (uint)-1) { 962 | std::cout << "Error - Invalid AC value\n"; 963 | return false; 964 | } 965 | skips += extraSkips; 966 | break; 967 | } 968 | } 969 | } 970 | return true; 971 | } 972 | else { // image->startOfSelection != 0 && image->successiveApproximationHigh != 0 973 | // AC refinement 974 | int positive = 1 << image->successiveApproximationLow; 975 | int negative = ((unsigned)-1) << image->successiveApproximationLow; 976 | int i = image->startOfSelection; 977 | if (skips == 0) { 978 | for (; i <= image->endOfSelection; ++i) { 979 | byte symbol = getNextSymbol(bitReader, acTable); 980 | if (symbol == (byte)-1) { 981 | std::cout << "Error - Invalid AC value\n"; 982 | return false; 983 | } 984 | 985 | byte numZeroes = symbol >> 4; 986 | byte coeffLength = symbol & 0x0F; 987 | int coeff = 0; 988 | 989 | if (coeffLength != 0) { 990 | if (coeffLength != 1) { 991 | std::cout << "Error - Invalid AC value\n"; 992 | return false; 993 | } 994 | switch (bitReader.readBit()) { 995 | case 1: 996 | coeff = positive; 997 | break; 998 | case 0: 999 | coeff = negative; 1000 | break; 1001 | default: // -1, data stream is empty 1002 | std::cout << "Error - Invalid AC value\n"; 1003 | return false; 1004 | } 1005 | } 1006 | else { 1007 | if (numZeroes != 15) { 1008 | skips = 1 << numZeroes; 1009 | uint extraSkips = bitReader.readBits(numZeroes); 1010 | if (extraSkips == (uint)-1) { 1011 | std::cout << "Error - Invalid AC value\n"; 1012 | return false; 1013 | } 1014 | skips += extraSkips; 1015 | break; 1016 | } 1017 | } 1018 | 1019 | do { 1020 | if (component[zigZagMap[i]] != 0) { 1021 | switch (bitReader.readBit()) { 1022 | case 1: 1023 | if ((component[zigZagMap[i]] & positive) == 0) { 1024 | if (component[zigZagMap[i]] >= 0) { 1025 | component[zigZagMap[i]] += positive; 1026 | } 1027 | else { 1028 | component[zigZagMap[i]] += negative; 1029 | } 1030 | } 1031 | break; 1032 | case 0: 1033 | // do nothing 1034 | break; 1035 | default: // -1, data stream is empty 1036 | std::cout << "Error - Invalid AC value\n"; 1037 | return false; 1038 | } 1039 | } 1040 | else { 1041 | if (numZeroes == 0) { 1042 | break; 1043 | } 1044 | numZeroes -= 1; 1045 | } 1046 | 1047 | i += 1; 1048 | } while (i <= image->endOfSelection); 1049 | 1050 | if (coeff != 0 && i <= image->endOfSelection) { 1051 | component[zigZagMap[i]] = coeff; 1052 | } 1053 | } 1054 | } 1055 | 1056 | if (skips > 0) { 1057 | for (; i <= image->endOfSelection; ++i) { 1058 | if (component[zigZagMap[i]] != 0) { 1059 | switch (bitReader.readBit()) { 1060 | case 1: 1061 | if ((component[zigZagMap[i]] & positive) == 0) { 1062 | if (component[zigZagMap[i]] >= 0) { 1063 | component[zigZagMap[i]] += positive; 1064 | } 1065 | else { 1066 | component[zigZagMap[i]] += negative; 1067 | } 1068 | } 1069 | break; 1070 | case 0: 1071 | // do nothing 1072 | break; 1073 | default: // -1, data stream is empty 1074 | std::cout << "Error - Invalid AC value\n"; 1075 | return false; 1076 | } 1077 | } 1078 | } 1079 | skips -= 1; 1080 | } 1081 | return true; 1082 | } 1083 | } 1084 | } 1085 | 1086 | // decode all the Huffman data and fill all MCUs 1087 | void decodeHuffmanData(BitReader& bitReader, JPGImage* const image) { 1088 | int previousDCs[3] = { 0 }; 1089 | uint skips = 0; 1090 | 1091 | const bool luminanceOnly = image->componentsInScan == 1 && image->colorComponents[0].usedInScan; 1092 | const uint yStep = luminanceOnly ? 1 : image->verticalSamplingFactor; 1093 | const uint xStep = luminanceOnly ? 1 : image->horizontalSamplingFactor; 1094 | const uint restartInterval = image->restartInterval * xStep * yStep; 1095 | 1096 | for (uint y = 0; y < image->blockHeight; y += yStep) { 1097 | for (uint x = 0; x < image->blockWidth; x += xStep) { 1098 | if (restartInterval != 0 && (y * image->blockWidthReal + x) % restartInterval == 0) { 1099 | previousDCs[0] = 0; 1100 | previousDCs[1] = 0; 1101 | previousDCs[2] = 0; 1102 | skips = 0; 1103 | bitReader.align(); 1104 | } 1105 | 1106 | for (uint i = 0; i < image->numComponents; ++i) { 1107 | const ColorComponent& component = image->colorComponents[i]; 1108 | if (component.usedInScan) { 1109 | const uint vMax = luminanceOnly ? 1 : component.verticalSamplingFactor; 1110 | const uint hMax = luminanceOnly ? 1 : component.horizontalSamplingFactor; 1111 | for (uint v = 0; v < vMax; ++v) { 1112 | for (uint h = 0; h < hMax; ++h) { 1113 | if (!decodeBlockComponent( 1114 | image, 1115 | bitReader, 1116 | image->blocks[(y + v) * image->blockWidthReal + (x + h)][i], 1117 | previousDCs[i], 1118 | skips, 1119 | image->huffmanDCTables[component.huffmanDCTableID], 1120 | image->huffmanACTables[component.huffmanACTableID])) { 1121 | return; 1122 | } 1123 | } 1124 | } 1125 | } 1126 | } 1127 | } 1128 | } 1129 | } 1130 | 1131 | // dequantize a block component based on a quantization table 1132 | void dequantizeBlockComponent(const QuantizationTable& qTable, int* const component) { 1133 | for (uint i = 0; i < 64; ++i) { 1134 | component[i] *= qTable.table[i]; 1135 | } 1136 | } 1137 | 1138 | // dequantize all MCUs 1139 | void dequantize(const JPGImage* const image) { 1140 | for (uint y = 0; y < image->blockHeight; y += image->verticalSamplingFactor) { 1141 | for (uint x = 0; x < image->blockWidth; x += image->horizontalSamplingFactor) { 1142 | for (uint i = 0; i < image->numComponents; ++i) { 1143 | const ColorComponent& component = image->colorComponents[i]; 1144 | for (uint v = 0; v < component.verticalSamplingFactor; ++v) { 1145 | for (uint h = 0; h < component.horizontalSamplingFactor; ++h) { 1146 | dequantizeBlockComponent(image->quantizationTables[component.quantizationTableID], 1147 | image->blocks[(y + v) * image->blockWidthReal + (x + h)][i]); 1148 | } 1149 | } 1150 | } 1151 | } 1152 | } 1153 | } 1154 | 1155 | // perform 1-D IDCT on all columns and rows of a block component 1156 | // resulting in 2-D IDCT 1157 | void inverseDCTBlockComponent(int* const component) { 1158 | 1159 | float intermediate[64]; 1160 | 1161 | for (uint i = 0; i < 8; ++i) { 1162 | const float g0 = component[0 * 8 + i] * s0; 1163 | const float g1 = component[4 * 8 + i] * s4; 1164 | const float g2 = component[2 * 8 + i] * s2; 1165 | const float g3 = component[6 * 8 + i] * s6; 1166 | const float g4 = component[5 * 8 + i] * s5; 1167 | const float g5 = component[1 * 8 + i] * s1; 1168 | const float g6 = component[7 * 8 + i] * s7; 1169 | const float g7 = component[3 * 8 + i] * s3; 1170 | 1171 | const float f0 = g0; 1172 | const float f1 = g1; 1173 | const float f2 = g2; 1174 | const float f3 = g3; 1175 | const float f4 = g4 - g7; 1176 | const float f5 = g5 + g6; 1177 | const float f6 = g5 - g6; 1178 | const float f7 = g4 + g7; 1179 | 1180 | const float e0 = f0; 1181 | const float e1 = f1; 1182 | const float e2 = f2 - f3; 1183 | const float e3 = f2 + f3; 1184 | const float e4 = f4; 1185 | const float e5 = f5 - f7; 1186 | const float e6 = f6; 1187 | const float e7 = f5 + f7; 1188 | const float e8 = f4 + f6; 1189 | 1190 | const float d0 = e0; 1191 | const float d1 = e1; 1192 | const float d2 = e2 * m1; 1193 | const float d3 = e3; 1194 | const float d4 = e4 * m2; 1195 | const float d5 = e5 * m3; 1196 | const float d6 = e6 * m4; 1197 | const float d7 = e7; 1198 | const float d8 = e8 * m5; 1199 | 1200 | const float c0 = d0 + d1; 1201 | const float c1 = d0 - d1; 1202 | const float c2 = d2 - d3; 1203 | const float c3 = d3; 1204 | const float c4 = d4 + d8; 1205 | const float c5 = d5 + d7; 1206 | const float c6 = d6 - d8; 1207 | const float c7 = d7; 1208 | const float c8 = c5 - c6; 1209 | 1210 | const float b0 = c0 + c3; 1211 | const float b1 = c1 + c2; 1212 | const float b2 = c1 - c2; 1213 | const float b3 = c0 - c3; 1214 | const float b4 = c4 - c8; 1215 | const float b5 = c8; 1216 | const float b6 = c6 - c7; 1217 | const float b7 = c7; 1218 | 1219 | intermediate[0 * 8 + i] = b0 + b7; 1220 | intermediate[1 * 8 + i] = b1 + b6; 1221 | intermediate[2 * 8 + i] = b2 + b5; 1222 | intermediate[3 * 8 + i] = b3 + b4; 1223 | intermediate[4 * 8 + i] = b3 - b4; 1224 | intermediate[5 * 8 + i] = b2 - b5; 1225 | intermediate[6 * 8 + i] = b1 - b6; 1226 | intermediate[7 * 8 + i] = b0 - b7; 1227 | } 1228 | for (uint i = 0; i < 8; ++i) { 1229 | const float g0 = intermediate[i * 8 + 0] * s0; 1230 | const float g1 = intermediate[i * 8 + 4] * s4; 1231 | const float g2 = intermediate[i * 8 + 2] * s2; 1232 | const float g3 = intermediate[i * 8 + 6] * s6; 1233 | const float g4 = intermediate[i * 8 + 5] * s5; 1234 | const float g5 = intermediate[i * 8 + 1] * s1; 1235 | const float g6 = intermediate[i * 8 + 7] * s7; 1236 | const float g7 = intermediate[i * 8 + 3] * s3; 1237 | 1238 | const float f0 = g0; 1239 | const float f1 = g1; 1240 | const float f2 = g2; 1241 | const float f3 = g3; 1242 | const float f4 = g4 - g7; 1243 | const float f5 = g5 + g6; 1244 | const float f6 = g5 - g6; 1245 | const float f7 = g4 + g7; 1246 | 1247 | const float e0 = f0; 1248 | const float e1 = f1; 1249 | const float e2 = f2 - f3; 1250 | const float e3 = f2 + f3; 1251 | const float e4 = f4; 1252 | const float e5 = f5 - f7; 1253 | const float e6 = f6; 1254 | const float e7 = f5 + f7; 1255 | const float e8 = f4 + f6; 1256 | 1257 | const float d0 = e0; 1258 | const float d1 = e1; 1259 | const float d2 = e2 * m1; 1260 | const float d3 = e3; 1261 | const float d4 = e4 * m2; 1262 | const float d5 = e5 * m3; 1263 | const float d6 = e6 * m4; 1264 | const float d7 = e7; 1265 | const float d8 = e8 * m5; 1266 | 1267 | const float c0 = d0 + d1; 1268 | const float c1 = d0 - d1; 1269 | const float c2 = d2 - d3; 1270 | const float c3 = d3; 1271 | const float c4 = d4 + d8; 1272 | const float c5 = d5 + d7; 1273 | const float c6 = d6 - d8; 1274 | const float c7 = d7; 1275 | const float c8 = c5 - c6; 1276 | 1277 | const float b0 = c0 + c3; 1278 | const float b1 = c1 + c2; 1279 | const float b2 = c1 - c2; 1280 | const float b3 = c0 - c3; 1281 | const float b4 = c4 - c8; 1282 | const float b5 = c8; 1283 | const float b6 = c6 - c7; 1284 | const float b7 = c7; 1285 | 1286 | component[i * 8 + 0] = b0 + b7 + 0.5f; 1287 | component[i * 8 + 1] = b1 + b6 + 0.5f; 1288 | component[i * 8 + 2] = b2 + b5 + 0.5f; 1289 | component[i * 8 + 3] = b3 + b4 + 0.5f; 1290 | component[i * 8 + 4] = b3 - b4 + 0.5f; 1291 | component[i * 8 + 5] = b2 - b5 + 0.5f; 1292 | component[i * 8 + 6] = b1 - b6 + 0.5f; 1293 | component[i * 8 + 7] = b0 - b7 + 0.5f; 1294 | } 1295 | } 1296 | 1297 | // perform IDCT on all MCUs 1298 | void inverseDCT(const JPGImage* const image) { 1299 | for (uint y = 0; y < image->blockHeight; y += image->verticalSamplingFactor) { 1300 | for (uint x = 0; x < image->blockWidth; x += image->horizontalSamplingFactor) { 1301 | for (uint i = 0; i < image->numComponents; ++i) { 1302 | const ColorComponent& component = image->colorComponents[i]; 1303 | for (uint v = 0; v < component.verticalSamplingFactor; ++v) { 1304 | for (uint h = 0; h < component.horizontalSamplingFactor; ++h) { 1305 | inverseDCTBlockComponent(image->blocks[(y + v) * image->blockWidthReal + (x + h)][i]); 1306 | } 1307 | } 1308 | } 1309 | } 1310 | } 1311 | } 1312 | 1313 | // convert all pixels in a block from YCbCr color space to RGB 1314 | void YCbCrToRGBBlock(Block& yBlock, const Block& cbcrBlock, const uint vSamp, const uint hSamp, const uint v, const uint h) { 1315 | for (uint y = 7; y < 8; --y) { 1316 | for (uint x = 7; x < 8; --x) { 1317 | const uint pixel = y * 8 + x; 1318 | const uint cbcrPixelRow = y / vSamp + 4 * v; 1319 | const uint cbcrPixelColumn = x / hSamp + 4 * h; 1320 | const uint cbcrPixel = cbcrPixelRow * 8 + cbcrPixelColumn; 1321 | int r = yBlock.y[pixel] + 1.402f * cbcrBlock.cr[cbcrPixel] + 128; 1322 | int g = yBlock.y[pixel] - 0.344f * cbcrBlock.cb[cbcrPixel] - 0.714f * cbcrBlock.cr[cbcrPixel] + 128; 1323 | int b = yBlock.y[pixel] + 1.772f * cbcrBlock.cb[cbcrPixel] + 128; 1324 | if (r < 0) r = 0; 1325 | if (r > 255) r = 255; 1326 | if (g < 0) g = 0; 1327 | if (g > 255) g = 255; 1328 | if (b < 0) b = 0; 1329 | if (b > 255) b = 255; 1330 | yBlock.r[pixel] = r; 1331 | yBlock.g[pixel] = g; 1332 | yBlock.b[pixel] = b; 1333 | } 1334 | } 1335 | } 1336 | 1337 | // convert all pixels from YCbCr color space to RGB 1338 | void YCbCrToRGB(const JPGImage* const image) { 1339 | const uint vSamp = image->verticalSamplingFactor; 1340 | const uint hSamp = image->horizontalSamplingFactor; 1341 | for (uint y = 0; y < image->blockHeight; y += vSamp) { 1342 | for (uint x = 0; x < image->blockWidth; x += hSamp) { 1343 | const Block& cbcrBlock = image->blocks[y * image->blockWidthReal + x]; 1344 | for (uint v = vSamp - 1; v < vSamp; --v) { 1345 | for (uint h = hSamp - 1; h < hSamp; --h) { 1346 | Block& yBlock = image->blocks[(y + v) * image->blockWidthReal + (x + h)]; 1347 | YCbCrToRGBBlock(yBlock, cbcrBlock, vSamp, hSamp, v, h); 1348 | } 1349 | } 1350 | } 1351 | } 1352 | } 1353 | 1354 | // helper function to write a 4-byte integer in little-endian 1355 | void putInt(byte*& bufferPos, const uint v) { 1356 | *bufferPos++ = v >> 0; 1357 | *bufferPos++ = v >> 8; 1358 | *bufferPos++ = v >> 16; 1359 | *bufferPos++ = v >> 24; 1360 | } 1361 | 1362 | // helper function to write a 2-byte short integer in little-endian 1363 | void putShort(byte*& bufferPos, const uint v) { 1364 | *bufferPos++ = v >> 0; 1365 | *bufferPos++ = v >> 8; 1366 | } 1367 | 1368 | // write all the pixels in the MCUs to a BMP file 1369 | void writeBMP(const JPGImage* const image, const std::string& filename) { 1370 | // open file 1371 | std::cout << "Writing " << filename << "...\n"; 1372 | std::ofstream outFile(filename, std::ios::out | std::ios::binary); 1373 | if (!outFile.is_open()) { 1374 | std::cout << "Error - Error opening output file\n"; 1375 | return; 1376 | } 1377 | 1378 | const uint paddingSize = image->width % 4; 1379 | const uint size = 14 + 12 + image->height * image->width * 3 + paddingSize * image->height; 1380 | 1381 | byte* buffer = new (std::nothrow) byte[size]; 1382 | if (buffer == nullptr) { 1383 | std::cout << "Error - Memory error\n"; 1384 | outFile.close(); 1385 | return; 1386 | } 1387 | byte* bufferPos = buffer; 1388 | 1389 | *bufferPos++ = 'B'; 1390 | *bufferPos++ = 'M'; 1391 | putInt(bufferPos, size); 1392 | putInt(bufferPos, 0); 1393 | putInt(bufferPos, 0x1A); 1394 | putInt(bufferPos, 12); 1395 | putShort(bufferPos, image->width); 1396 | putShort(bufferPos, image->height); 1397 | putShort(bufferPos, 1); 1398 | putShort(bufferPos, 24); 1399 | 1400 | for (uint y = image->height - 1; y < image->height; --y) { 1401 | const uint blockRow = y / 8; 1402 | const uint pixelRow = y % 8; 1403 | for (uint x = 0; x < image->width; ++x) { 1404 | const uint blockColumn = x / 8; 1405 | const uint pixelColumn = x % 8; 1406 | const uint blockIndex = blockRow * image->blockWidthReal + blockColumn; 1407 | const uint pixelIndex = pixelRow * 8 + pixelColumn; 1408 | *bufferPos++ = image->blocks[blockIndex].b[pixelIndex]; 1409 | *bufferPos++ = image->blocks[blockIndex].g[pixelIndex]; 1410 | *bufferPos++ = image->blocks[blockIndex].r[pixelIndex]; 1411 | } 1412 | for (uint i = 0; i < paddingSize; ++i) { 1413 | *bufferPos++ = 0; 1414 | } 1415 | } 1416 | 1417 | outFile.write((char*)buffer, size); 1418 | outFile.close(); 1419 | delete[] buffer; 1420 | } 1421 | 1422 | int main(int argc, char** argv) { 1423 | // validate arguments 1424 | if (argc < 2) { 1425 | std::cout << "Error - Invalid arguments\n"; 1426 | return 1; 1427 | } 1428 | 1429 | for (int i = 1; i < argc; ++i) { 1430 | const std::string filename(argv[i]); 1431 | 1432 | // read image 1433 | JPGImage* image = readJPG(filename); 1434 | // validate image 1435 | if (image == nullptr) { 1436 | continue; 1437 | } 1438 | if (image->blocks == nullptr) { 1439 | delete image; 1440 | continue; 1441 | } 1442 | if (image->valid == false) { 1443 | delete[] image->blocks; 1444 | delete image; 1445 | continue; 1446 | } 1447 | 1448 | // dequantize DCT coefficients 1449 | dequantize(image); 1450 | 1451 | // Inverse Discrete Cosine Transform 1452 | inverseDCT(image); 1453 | 1454 | // color conversion 1455 | YCbCrToRGB(image); 1456 | 1457 | // write BMP file 1458 | const std::size_t pos = filename.find_last_of('.'); 1459 | const std::string outFilename = (pos == std::string::npos) ? 1460 | (filename + ".bmp") : 1461 | (filename.substr(0, pos) + ".bmp"); 1462 | writeBMP(image, outFilename); 1463 | 1464 | delete[] image->blocks; 1465 | delete image; 1466 | } 1467 | return 0; 1468 | } 1469 | -------------------------------------------------------------------------------- /src/encoder.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "jpg.h" 6 | 7 | // helper function to read a 4-byte integer in little-endian 8 | uint getInt(std::ifstream& inFile) { 9 | return (inFile.get() << 0) 10 | + (inFile.get() << 8) 11 | + (inFile.get() << 16) 12 | + (inFile.get() << 24); 13 | } 14 | 15 | // helper function to read a 2-byte short integer in little-endian 16 | uint getShort(std::ifstream& inFile) { 17 | return (inFile.get() << 0) 18 | + (inFile.get() << 8); 19 | } 20 | 21 | BMPImage readBMP(const std::string& filename) { 22 | BMPImage image; 23 | 24 | // open file 25 | std::cout << "Reading " << filename << "...\n"; 26 | std::ifstream inFile(filename, std::ios::in | std::ios::binary); 27 | if (!inFile.is_open()) { 28 | std::cout << "Error - Error opening input file\n"; 29 | return image; 30 | } 31 | 32 | if (inFile.get() != 'B' || inFile.get() != 'M') { 33 | std::cout << "Error - Invalid BMP file\n"; 34 | inFile.close(); 35 | return image; 36 | } 37 | 38 | getInt(inFile); // size 39 | getInt(inFile); // nothing 40 | if (getInt(inFile) != 0x1A) { 41 | std::cout << "Error - Invalid offset\n"; 42 | inFile.close(); 43 | return image; 44 | } 45 | if (getInt(inFile) != 12) { 46 | std::cout << "Error - Invalid DIB size\n"; 47 | inFile.close(); 48 | return image; 49 | } 50 | image.width = getShort(inFile); 51 | image.height = getShort(inFile); 52 | if (getShort(inFile) != 1) { 53 | std::cout << "Error - Invalid number of planes\n"; 54 | inFile.close(); 55 | return image; 56 | } 57 | if (getShort(inFile) != 24) { 58 | std::cout << "Error - Invalid bit depth\n"; 59 | inFile.close(); 60 | return image; 61 | } 62 | 63 | if (image.height == 0 || image.width == 0) { 64 | std::cout << "Error - Invalid dimensions\n"; 65 | inFile.close(); 66 | return image; 67 | } 68 | 69 | image.blockHeight = (image.height + 7) / 8; 70 | image.blockWidth = (image.width + 7) / 8; 71 | 72 | image.blocks = new (std::nothrow) Block[image.blockHeight * image.blockWidth]; 73 | if (image.blocks == nullptr) { 74 | std::cout << "Error - Memory error\n"; 75 | inFile.close(); 76 | return image; 77 | } 78 | 79 | const uint paddingSize = image.width % 4; 80 | 81 | for (uint y = image.height - 1; y < image.height; --y) { 82 | const uint blockRow = y / 8; 83 | const uint pixelRow = y % 8; 84 | for (uint x = 0; x < image.width; ++x) { 85 | const uint blockColumn = x / 8; 86 | const uint pixelColumn = x % 8; 87 | const uint blockIndex = blockRow * image.blockWidth + blockColumn; 88 | const uint pixelIndex = pixelRow * 8 + pixelColumn; 89 | image.blocks[blockIndex].b[pixelIndex] = inFile.get(); 90 | image.blocks[blockIndex].g[pixelIndex] = inFile.get(); 91 | image.blocks[blockIndex].r[pixelIndex] = inFile.get(); 92 | } 93 | for (uint i = 0; i < paddingSize; ++i) { 94 | inFile.get(); 95 | } 96 | } 97 | 98 | inFile.close(); 99 | return image; 100 | } 101 | 102 | // convert all pixels in a block from RGB color space to YCbCr 103 | void RGBToYCbCrBlock(Block& block) { 104 | for (uint y = 0; y < 8; ++y) { 105 | for (uint x = 0; x < 8; ++x) { 106 | const uint pixel = y * 8 + x; 107 | int y = 0.2990 * block.r[pixel] + 0.5870 * block.g[pixel] + 0.1140 * block.b[pixel] - 128; 108 | int cb = -0.1687 * block.r[pixel] - 0.3313 * block.g[pixel] + 0.5000 * block.b[pixel]; 109 | int cr = 0.5000 * block.r[pixel] - 0.4187 * block.g[pixel] - 0.0813 * block.b[pixel]; 110 | if (y < -128) y = -128; 111 | if (y > 127) y = 127; 112 | if (cb < -128) cb = -128; 113 | if (cb > 127) cb = 127; 114 | if (cr < -128) cr = -128; 115 | if (cr > 127) cr = 127; 116 | block.y[pixel] = y; 117 | block.cb[pixel] = cb; 118 | block.cr[pixel] = cr; 119 | } 120 | } 121 | } 122 | 123 | // convert all pixels from RGB color space to YCbCr 124 | void RGBToYCbCr(const BMPImage& image) { 125 | for (uint y = 0; y < image.blockHeight; ++y) { 126 | for (uint x = 0; x < image.blockWidth; ++x) { 127 | RGBToYCbCrBlock(image.blocks[y * image.blockWidth + x]); 128 | } 129 | } 130 | } 131 | 132 | // perform 1-D FDCT on all columns and rows of a block component 133 | // resulting in 2-D FDCT 134 | void forwardDCTBlockComponent(int* const component) { 135 | for (uint i = 0; i < 8; ++i) { 136 | const float a0 = component[0 * 8 + i]; 137 | const float a1 = component[1 * 8 + i]; 138 | const float a2 = component[2 * 8 + i]; 139 | const float a3 = component[3 * 8 + i]; 140 | const float a4 = component[4 * 8 + i]; 141 | const float a5 = component[5 * 8 + i]; 142 | const float a6 = component[6 * 8 + i]; 143 | const float a7 = component[7 * 8 + i]; 144 | 145 | const float b0 = a0 + a7; 146 | const float b1 = a1 + a6; 147 | const float b2 = a2 + a5; 148 | const float b3 = a3 + a4; 149 | const float b4 = a3 - a4; 150 | const float b5 = a2 - a5; 151 | const float b6 = a1 - a6; 152 | const float b7 = a0 - a7; 153 | 154 | const float c0 = b0 + b3; 155 | const float c1 = b1 + b2; 156 | const float c2 = b1 - b2; 157 | const float c3 = b0 - b3; 158 | const float c4 = b4; 159 | const float c5 = b5 - b4; 160 | const float c6 = b6 - c5; 161 | const float c7 = b7 - b6; 162 | 163 | const float d0 = c0 + c1; 164 | const float d1 = c0 - c1; 165 | const float d2 = c2; 166 | const float d3 = c3 - c2; 167 | const float d4 = c4; 168 | const float d5 = c5; 169 | const float d6 = c6; 170 | const float d7 = c5 + c7; 171 | const float d8 = c4 - c6; 172 | 173 | const float e0 = d0; 174 | const float e1 = d1; 175 | const float e2 = d2 * m1; 176 | const float e3 = d3; 177 | const float e4 = d4 * m2; 178 | const float e5 = d5 * m3; 179 | const float e6 = d6 * m4; 180 | const float e7 = d7; 181 | const float e8 = d8 * m5; 182 | 183 | const float f0 = e0; 184 | const float f1 = e1; 185 | const float f2 = e2 + e3; 186 | const float f3 = e3 - e2; 187 | const float f4 = e4 + e8; 188 | const float f5 = e5 + e7; 189 | const float f6 = e6 + e8; 190 | const float f7 = e7 - e5; 191 | 192 | const float g0 = f0; 193 | const float g1 = f1; 194 | const float g2 = f2; 195 | const float g3 = f3; 196 | const float g4 = f4 + f7; 197 | const float g5 = f5 + f6; 198 | const float g6 = f5 - f6; 199 | const float g7 = f7 - f4; 200 | 201 | component[0 * 8 + i] = g0 * s0; 202 | component[4 * 8 + i] = g1 * s4; 203 | component[2 * 8 + i] = g2 * s2; 204 | component[6 * 8 + i] = g3 * s6; 205 | component[5 * 8 + i] = g4 * s5; 206 | component[1 * 8 + i] = g5 * s1; 207 | component[7 * 8 + i] = g6 * s7; 208 | component[3 * 8 + i] = g7 * s3; 209 | } 210 | for (uint i = 0; i < 8; ++i) { 211 | const float a0 = component[i * 8 + 0]; 212 | const float a1 = component[i * 8 + 1]; 213 | const float a2 = component[i * 8 + 2]; 214 | const float a3 = component[i * 8 + 3]; 215 | const float a4 = component[i * 8 + 4]; 216 | const float a5 = component[i * 8 + 5]; 217 | const float a6 = component[i * 8 + 6]; 218 | const float a7 = component[i * 8 + 7]; 219 | 220 | const float b0 = a0 + a7; 221 | const float b1 = a1 + a6; 222 | const float b2 = a2 + a5; 223 | const float b3 = a3 + a4; 224 | const float b4 = a3 - a4; 225 | const float b5 = a2 - a5; 226 | const float b6 = a1 - a6; 227 | const float b7 = a0 - a7; 228 | 229 | const float c0 = b0 + b3; 230 | const float c1 = b1 + b2; 231 | const float c2 = b1 - b2; 232 | const float c3 = b0 - b3; 233 | const float c4 = b4; 234 | const float c5 = b5 - b4; 235 | const float c6 = b6 - c5; 236 | const float c7 = b7 - b6; 237 | 238 | const float d0 = c0 + c1; 239 | const float d1 = c0 - c1; 240 | const float d2 = c2; 241 | const float d3 = c3 - c2; 242 | const float d4 = c4; 243 | const float d5 = c5; 244 | const float d6 = c6; 245 | const float d7 = c5 + c7; 246 | const float d8 = c4 - c6; 247 | 248 | const float e0 = d0; 249 | const float e1 = d1; 250 | const float e2 = d2 * m1; 251 | const float e3 = d3; 252 | const float e4 = d4 * m2; 253 | const float e5 = d5 * m3; 254 | const float e6 = d6 * m4; 255 | const float e7 = d7; 256 | const float e8 = d8 * m5; 257 | 258 | const float f0 = e0; 259 | const float f1 = e1; 260 | const float f2 = e2 + e3; 261 | const float f3 = e3 - e2; 262 | const float f4 = e4 + e8; 263 | const float f5 = e5 + e7; 264 | const float f6 = e6 + e8; 265 | const float f7 = e7 - e5; 266 | 267 | const float g0 = f0; 268 | const float g1 = f1; 269 | const float g2 = f2; 270 | const float g3 = f3; 271 | const float g4 = f4 + f7; 272 | const float g5 = f5 + f6; 273 | const float g6 = f5 - f6; 274 | const float g7 = f7 - f4; 275 | 276 | component[i * 8 + 0] = g0 * s0; 277 | component[i * 8 + 4] = g1 * s4; 278 | component[i * 8 + 2] = g2 * s2; 279 | component[i * 8 + 6] = g3 * s6; 280 | component[i * 8 + 5] = g4 * s5; 281 | component[i * 8 + 1] = g5 * s1; 282 | component[i * 8 + 7] = g6 * s7; 283 | component[i * 8 + 3] = g7 * s3; 284 | } 285 | } 286 | 287 | // perform FDCT on all MCUs 288 | void forwardDCT(const BMPImage& image) { 289 | for (uint y = 0; y < image.blockHeight; ++y) { 290 | for (uint x = 0; x < image.blockWidth; ++x) { 291 | for (uint i = 0; i < 3; ++i) { 292 | forwardDCTBlockComponent(image.blocks[y * image.blockWidth + x][i]); 293 | } 294 | } 295 | } 296 | } 297 | 298 | // quantize a block component based on a quantization table 299 | void quantizeBlockComponent(const QuantizationTable& qTable, int* const component) { 300 | for (uint i = 0; i < 64; ++i) { 301 | component[i] /= (signed)qTable.table[i]; 302 | } 303 | } 304 | 305 | // quantize all MCUs 306 | void quantize(const BMPImage& image) { 307 | for (uint y = 0; y < image.blockHeight; ++y) { 308 | for (uint x = 0; x < image.blockWidth; ++x) { 309 | for (uint i = 0; i < 3; ++i) { 310 | quantizeBlockComponent(*qTables100[i], image.blocks[y * image.blockWidth + x][i]); 311 | } 312 | } 313 | } 314 | } 315 | 316 | class BitWriter { 317 | private: 318 | byte nextBit = 0; 319 | std::vector& data; 320 | 321 | public: 322 | BitWriter(std::vector& d) : 323 | data(d) 324 | {} 325 | 326 | void writeBit(uint bit) { 327 | if (nextBit == 0) { 328 | data.push_back(0); 329 | } 330 | data.back() |= (bit & 1) << (7 - nextBit); 331 | nextBit = (nextBit + 1) % 8; 332 | if (nextBit == 0 && data.back() == 0xFF) { 333 | data.push_back(0); 334 | } 335 | } 336 | 337 | void writeBits(uint bits, uint length) { 338 | for (uint i = 1; i <= length; ++i) { 339 | writeBit(bits >> (length - i)); 340 | } 341 | } 342 | }; 343 | 344 | // generate all Huffman codes based on symbols from a Huffman table 345 | void generateCodes(HuffmanTable& hTable) { 346 | uint code = 0; 347 | for (uint i = 0; i < 16; ++i) { 348 | for (uint j = hTable.offsets[i]; j < hTable.offsets[i + 1]; ++j) { 349 | hTable.codes[j] = code; 350 | code += 1; 351 | } 352 | code <<= 1; 353 | } 354 | } 355 | 356 | uint bitLength(int v) { 357 | uint length = 0; 358 | while (v > 0) { 359 | v >>= 1; 360 | length += 1; 361 | } 362 | return length; 363 | } 364 | 365 | bool getCode(const HuffmanTable& hTable, byte symbol, uint& code, uint& codeLength) { 366 | for (uint i = 0; i < 16; ++i) { 367 | for (uint j = hTable.offsets[i]; j < hTable.offsets[i + 1]; ++j) { 368 | if (symbol == hTable.symbols[j]) { 369 | code = hTable.codes[j]; 370 | codeLength = i + 1; 371 | return true; 372 | } 373 | } 374 | } 375 | return false; 376 | } 377 | 378 | bool encodeBlockComponent( 379 | BitWriter& bitWriter, 380 | int* const component, 381 | int& previousDC, 382 | const HuffmanTable& dcTable, 383 | const HuffmanTable& acTable 384 | ) { 385 | // encode DC value 386 | int coeff = component[0] - previousDC; 387 | previousDC = component[0]; 388 | 389 | uint coeffLength = bitLength(std::abs(coeff)); 390 | if (coeffLength > 11) { 391 | std::cout << "Error - DC coefficient length greater than 11\n"; 392 | return false; 393 | } 394 | if (coeff < 0) { 395 | coeff += (1 << coeffLength) - 1; 396 | } 397 | 398 | uint code = 0; 399 | uint codeLength = 0; 400 | if (!getCode(dcTable, coeffLength, code, codeLength)) { 401 | std::cout << "Error - Invalid DC value\n"; 402 | return false; 403 | } 404 | bitWriter.writeBits(code, codeLength); 405 | bitWriter.writeBits(coeff, coeffLength); 406 | 407 | // encode AC values 408 | for (uint i = 1; i < 64; ++i) { 409 | // find zero run length 410 | byte numZeroes = 0; 411 | while (i < 64 && component[zigZagMap[i]] == 0) { 412 | numZeroes += 1; 413 | i += 1; 414 | } 415 | 416 | if (i == 64) { 417 | if (!getCode(acTable, 0x00, code, codeLength)) { 418 | std::cout << "Error - Invalid AC value\n"; 419 | return false; 420 | } 421 | bitWriter.writeBits(code, codeLength); 422 | return true; 423 | } 424 | 425 | while (numZeroes >= 16) { 426 | if (!getCode(acTable, 0xF0, code, codeLength)) { 427 | std::cout << "Error - Invalid AC value\n"; 428 | return false; 429 | } 430 | bitWriter.writeBits(code, codeLength); 431 | numZeroes -= 16; 432 | } 433 | 434 | // find coeff length 435 | coeff = component[zigZagMap[i]]; 436 | coeffLength = bitLength(std::abs(coeff)); 437 | if (coeffLength > 10) { 438 | std::cout << "Error - AC coefficient length greater than 10\n"; 439 | return false; 440 | } 441 | if (coeff < 0) { 442 | coeff += (1 << coeffLength) - 1; 443 | } 444 | 445 | // find symbol in table 446 | byte symbol = numZeroes << 4 | coeffLength; 447 | if (!getCode(acTable, symbol, code, codeLength)) { 448 | std::cout << "Error - Invalid AC value\n"; 449 | return false; 450 | } 451 | bitWriter.writeBits(code, codeLength); 452 | bitWriter.writeBits(coeff, coeffLength); 453 | } 454 | 455 | return true; 456 | } 457 | 458 | // encode all the Huffman data from all MCUs 459 | std::vector encodeHuffmanData(const BMPImage& image) { 460 | std::vector huffmanData; 461 | BitWriter bitWriter(huffmanData); 462 | 463 | int previousDCs[3] = { 0 }; 464 | 465 | for (uint i = 0; i < 3; ++i) { 466 | if (!dcTables[i]->set) { 467 | generateCodes(*dcTables[i]); 468 | dcTables[i]->set = true; 469 | } 470 | if (!acTables[i]->set) { 471 | generateCodes(*acTables[i]); 472 | acTables[i]->set = true; 473 | } 474 | } 475 | 476 | for (uint y = 0; y < image.blockHeight; ++y) { 477 | for (uint x = 0; x < image.blockWidth; ++x) { 478 | for (uint i = 0; i < 3; ++i) { 479 | if (!encodeBlockComponent( 480 | bitWriter, 481 | image.blocks[y * image.blockWidth + x][i], 482 | previousDCs[i], 483 | *dcTables[i], 484 | *acTables[i])) { 485 | return std::vector(); 486 | } 487 | } 488 | } 489 | } 490 | 491 | return huffmanData; 492 | } 493 | 494 | // helper function to write a 2-byte short integer in big-endian 495 | void putShort(std::ofstream& outFile, const uint v) { 496 | outFile.put((v >> 8) & 0xFF); 497 | outFile.put((v >> 0) & 0xFF); 498 | } 499 | 500 | void writeQuantizationTable(std::ofstream& outFile, byte tableID, const QuantizationTable& qTable) { 501 | outFile.put(0xFF); 502 | outFile.put(DQT); 503 | putShort(outFile, 67); 504 | outFile.put(tableID); 505 | for (uint i = 0; i < 64; ++i) { 506 | outFile.put(qTable.table[zigZagMap[i]]); 507 | } 508 | } 509 | 510 | void writeStartOfFrame(std::ofstream& outFile, const BMPImage& image) { 511 | outFile.put(0xFF); 512 | outFile.put(SOF0); 513 | putShort(outFile, 17); 514 | outFile.put(8); 515 | putShort(outFile, image.height); 516 | putShort(outFile, image.width); 517 | outFile.put(3); 518 | for (uint i = 1; i <= 3; ++i) { 519 | outFile.put(i); 520 | outFile.put(0x11); 521 | outFile.put(i == 1 ? 0 : 1); 522 | } 523 | } 524 | 525 | void writeHuffmanTable(std::ofstream& outFile, byte acdc, byte tableID, const HuffmanTable& hTable) { 526 | outFile.put(0xFF); 527 | outFile.put(DHT); 528 | putShort(outFile, 19 + hTable.offsets[16]); 529 | outFile.put(acdc << 4 | tableID); 530 | for (uint i = 0; i < 16; ++i) { 531 | outFile.put(hTable.offsets[i + 1] - hTable.offsets[i]); 532 | } 533 | for (uint i = 0; i < 16; ++i) { 534 | for (uint j = hTable.offsets[i]; j < hTable.offsets[i + 1]; ++j) { 535 | outFile.put(hTable.symbols[j]); 536 | } 537 | } 538 | } 539 | 540 | void writeStartOfScan(std::ofstream& outFile) { 541 | outFile.put(0xFF); 542 | outFile.put(SOS); 543 | putShort(outFile, 12); 544 | outFile.put(3); 545 | for (uint i = 1; i <= 3; ++i) { 546 | outFile.put(i); 547 | outFile.put(i == 1 ? 0x00 : 0x11); 548 | } 549 | outFile.put(0); 550 | outFile.put(63); 551 | outFile.put(0); 552 | } 553 | 554 | void writeAPP0(std::ofstream& outFile) { 555 | outFile.put(0xFF); 556 | outFile.put(APP0); 557 | putShort(outFile, 16); 558 | outFile.put('J'); 559 | outFile.put('F'); 560 | outFile.put('I'); 561 | outFile.put('F'); 562 | outFile.put(0); 563 | outFile.put(1); 564 | outFile.put(2); 565 | outFile.put(0); 566 | putShort(outFile, 100); 567 | putShort(outFile, 100); 568 | outFile.put(0); 569 | outFile.put(0); 570 | } 571 | 572 | void writeJPG(const BMPImage& image, const std::string& filename) { 573 | std::vector huffmanData = encodeHuffmanData(image); 574 | if (huffmanData.size() == 0) { 575 | return; 576 | } 577 | 578 | // open file 579 | std::cout << "Writing " << filename << "...\n"; 580 | std::ofstream outFile(filename, std::ios::out | std::ios::binary); 581 | if (!outFile.is_open()) { 582 | std::cout << "Error - Error opening output file\n"; 583 | return; 584 | } 585 | 586 | // SOI 587 | outFile.put(0xFF); 588 | outFile.put(SOI); 589 | 590 | // APP0 591 | writeAPP0(outFile); 592 | 593 | // DQT 594 | writeQuantizationTable(outFile, 0, qTableY100); 595 | writeQuantizationTable(outFile, 1, qTableCbCr100); 596 | 597 | // SOF 598 | writeStartOfFrame(outFile, image); 599 | 600 | // DHT 601 | writeHuffmanTable(outFile, 0, 0, hDCTableY); 602 | writeHuffmanTable(outFile, 0, 1, hDCTableCbCr); 603 | writeHuffmanTable(outFile, 1, 0, hACTableY); 604 | writeHuffmanTable(outFile, 1, 1, hACTableCbCr); 605 | 606 | // SOS 607 | writeStartOfScan(outFile); 608 | 609 | // ECS 610 | outFile.write((char*)&huffmanData[0], huffmanData.size()); 611 | 612 | // EOI 613 | outFile.put(0xFF); 614 | outFile.put(EOI); 615 | 616 | outFile.close(); 617 | } 618 | 619 | int main(int argc, char** argv) { 620 | // validate arguments 621 | if (argc < 2) { 622 | std::cout << "Error - Invalid arguments\n"; 623 | return 1; 624 | } 625 | 626 | for (int i = 1; i < argc; ++i) { 627 | const std::string filename(argv[i]); 628 | 629 | // read image 630 | BMPImage image = readBMP(filename); 631 | // validate image 632 | if (image.blocks == nullptr) { 633 | continue; 634 | } 635 | 636 | // color conversion 637 | RGBToYCbCr(image); 638 | 639 | // Forward Discrete Cosine Transform 640 | forwardDCT(image); 641 | 642 | // quantize DCT coefficients 643 | quantize(image); 644 | 645 | // write JPG file 646 | const std::size_t pos = filename.find_last_of('.'); 647 | const std::string outFilename = (pos == std::string::npos) ? 648 | (filename + ".jpg") : 649 | (filename.substr(0, pos) + ".jpg"); 650 | writeJPG(image, outFilename); 651 | 652 | delete[] image.blocks; 653 | } 654 | return 0; 655 | } 656 | -------------------------------------------------------------------------------- /src/jpg.h: -------------------------------------------------------------------------------- 1 | #ifndef JPG_H 2 | #define JPG_H 3 | 4 | #define _USE_MATH_DEFINES 5 | #include 6 | 7 | typedef unsigned char byte; 8 | typedef unsigned int uint; 9 | 10 | // Start of Frame markers, non-differential, Huffman coding 11 | const byte SOF0 = 0xC0; // Baseline DCT 12 | const byte SOF1 = 0xC1; // Extended sequential DCT 13 | const byte SOF2 = 0xC2; // Progressive DCT 14 | const byte SOF3 = 0xC3; // Lossless (sequential) 15 | 16 | // Start of Frame markers, differential, Huffman coding 17 | const byte SOF5 = 0xC5; // Differential sequential DCT 18 | const byte SOF6 = 0xC6; // Differential progressive DCT 19 | const byte SOF7 = 0xC7; // Differential lossless (sequential) 20 | 21 | // Start of Frame markers, non-differential, arithmetic coding 22 | const byte SOF9 = 0xC9; // Extended sequential DCT 23 | const byte SOF10 = 0xCA; // Progressive DCT 24 | const byte SOF11 = 0xCB; // Lossless (sequential) 25 | 26 | // Start of Frame markers, differential, arithmetic coding 27 | const byte SOF13 = 0xCD; // Differential sequential DCT 28 | const byte SOF14 = 0xCE; // Differential progressive DCT 29 | const byte SOF15 = 0xCF; // Differential lossless (sequential) 30 | 31 | // Define Huffman Table(s) 32 | const byte DHT = 0xC4; 33 | 34 | // JPEG extensions 35 | const byte JPG = 0xC8; 36 | 37 | // Define Arithmetic Coding Conditioning(s) 38 | const byte DAC = 0xCC; 39 | 40 | // Restart interval Markers 41 | const byte RST0 = 0xD0; 42 | const byte RST1 = 0xD1; 43 | const byte RST2 = 0xD2; 44 | const byte RST3 = 0xD3; 45 | const byte RST4 = 0xD4; 46 | const byte RST5 = 0xD5; 47 | const byte RST6 = 0xD6; 48 | const byte RST7 = 0xD7; 49 | 50 | // Other Markers 51 | const byte SOI = 0xD8; // Start of Image 52 | const byte EOI = 0xD9; // End of Image 53 | const byte SOS = 0xDA; // Start of Scan 54 | const byte DQT = 0xDB; // Define Quantization Table(s) 55 | const byte DNL = 0xDC; // Define Number of Lines 56 | const byte DRI = 0xDD; // Define Restart Interval 57 | const byte DHP = 0xDE; // Define Hierarchical Progression 58 | const byte EXP = 0xDF; // Expand Reference Component(s) 59 | 60 | // APPN Markers 61 | const byte APP0 = 0xE0; 62 | const byte APP1 = 0xE1; 63 | const byte APP2 = 0xE2; 64 | const byte APP3 = 0xE3; 65 | const byte APP4 = 0xE4; 66 | const byte APP5 = 0xE5; 67 | const byte APP6 = 0xE6; 68 | const byte APP7 = 0xE7; 69 | const byte APP8 = 0xE8; 70 | const byte APP9 = 0xE9; 71 | const byte APP10 = 0xEA; 72 | const byte APP11 = 0xEB; 73 | const byte APP12 = 0xEC; 74 | const byte APP13 = 0xED; 75 | const byte APP14 = 0xEE; 76 | const byte APP15 = 0xEF; 77 | 78 | // Misc Markers 79 | const byte JPG0 = 0xF0; 80 | const byte JPG1 = 0xF1; 81 | const byte JPG2 = 0xF2; 82 | const byte JPG3 = 0xF3; 83 | const byte JPG4 = 0xF4; 84 | const byte JPG5 = 0xF5; 85 | const byte JPG6 = 0xF6; 86 | const byte JPG7 = 0xF7; 87 | const byte JPG8 = 0xF8; 88 | const byte JPG9 = 0xF9; 89 | const byte JPG10 = 0xFA; 90 | const byte JPG11 = 0xFB; 91 | const byte JPG12 = 0xFC; 92 | const byte JPG13 = 0xFD; 93 | const byte COM = 0xFE; 94 | const byte TEM = 0x01; 95 | 96 | struct QuantizationTable { 97 | uint table[64] = { 0 }; 98 | bool set = false; 99 | }; 100 | 101 | struct HuffmanTable { 102 | byte offsets[17] = { 0 }; 103 | byte symbols[176] = { 0 }; 104 | uint codes[176] = { 0 }; 105 | bool set = false; 106 | }; 107 | 108 | struct ColorComponent { 109 | byte horizontalSamplingFactor = 0; 110 | byte verticalSamplingFactor = 0; 111 | byte quantizationTableID = 0; 112 | byte huffmanDCTableID = 0; 113 | byte huffmanACTableID = 0; 114 | bool usedInFrame = false; 115 | bool usedInScan = false; 116 | }; 117 | 118 | struct Block { 119 | union { 120 | int y[64] = { 0 }; 121 | int r[64]; 122 | }; 123 | union { 124 | int cb[64] = { 0 }; 125 | int g [64]; 126 | }; 127 | union { 128 | int cr[64] = { 0 }; 129 | int b [64]; 130 | }; 131 | int* operator[](uint i) { 132 | switch (i) { 133 | case 0: 134 | return y; 135 | case 1: 136 | return cb; 137 | case 2: 138 | return cr; 139 | default: 140 | return nullptr; 141 | } 142 | } 143 | }; 144 | 145 | struct JPGImage { 146 | QuantizationTable quantizationTables[4]; 147 | HuffmanTable huffmanDCTables[4]; 148 | HuffmanTable huffmanACTables[4]; 149 | ColorComponent colorComponents[3]; 150 | 151 | byte frameType = 0; 152 | uint height = 0; 153 | uint width = 0; 154 | byte numComponents = 0; 155 | bool zeroBased = false; 156 | 157 | byte componentsInScan = 0; 158 | byte startOfSelection = 0; 159 | byte endOfSelection = 0; 160 | byte successiveApproximationHigh = 0; 161 | byte successiveApproximationLow = 0; 162 | 163 | uint restartInterval = 0; 164 | 165 | Block* blocks = nullptr; 166 | 167 | bool valid = true; 168 | 169 | uint blockHeight = 0; 170 | uint blockWidth = 0; 171 | uint blockHeightReal = 0; 172 | uint blockWidthReal = 0; 173 | 174 | byte horizontalSamplingFactor = 0; 175 | byte verticalSamplingFactor = 0; 176 | }; 177 | 178 | struct BMPImage { 179 | uint height = 0; 180 | uint width = 0; 181 | 182 | Block* blocks = nullptr; 183 | 184 | uint blockHeight = 0; 185 | uint blockWidth = 0; 186 | }; 187 | 188 | const byte zigZagMap[] = { 189 | 0, 1, 8, 16, 9, 2, 3, 10, 190 | 17, 24, 32, 25, 18, 11, 4, 5, 191 | 12, 19, 26, 33, 40, 48, 41, 34, 192 | 27, 20, 13, 6, 7, 14, 21, 28, 193 | 35, 42, 49, 56, 57, 50, 43, 36, 194 | 29, 22, 15, 23, 30, 37, 44, 51, 195 | 58, 59, 52, 45, 38, 31, 39, 46, 196 | 53, 60, 61, 54, 47, 55, 62, 63 197 | }; 198 | 199 | // IDCT scaling factors 200 | const float m0 = 2.0 * std::cos(1.0 / 16.0 * 2.0 * M_PI); 201 | const float m1 = 2.0 * std::cos(2.0 / 16.0 * 2.0 * M_PI); 202 | const float m3 = 2.0 * std::cos(2.0 / 16.0 * 2.0 * M_PI); 203 | const float m5 = 2.0 * std::cos(3.0 / 16.0 * 2.0 * M_PI); 204 | const float m2 = m0 - m5; 205 | const float m4 = m0 + m5; 206 | 207 | const float s0 = std::cos(0.0 / 16.0 * M_PI) / std::sqrt(8); 208 | const float s1 = std::cos(1.0 / 16.0 * M_PI) / 2.0; 209 | const float s2 = std::cos(2.0 / 16.0 * M_PI) / 2.0; 210 | const float s3 = std::cos(3.0 / 16.0 * M_PI) / 2.0; 211 | const float s4 = std::cos(4.0 / 16.0 * M_PI) / 2.0; 212 | const float s5 = std::cos(5.0 / 16.0 * M_PI) / 2.0; 213 | const float s6 = std::cos(6.0 / 16.0 * M_PI) / 2.0; 214 | const float s7 = std::cos(7.0 / 16.0 * M_PI) / 2.0; 215 | 216 | // standard tables 217 | 218 | const QuantizationTable qTableY50 = { 219 | { 220 | 16, 11, 10, 16, 24, 40, 51, 61, 221 | 12, 12, 14, 19, 26, 58, 60, 55, 222 | 14, 13, 16, 24, 40, 57, 69, 56, 223 | 14, 17, 22, 29, 51, 87, 80, 62, 224 | 18, 22, 37, 56, 68, 109, 103, 77, 225 | 24, 35, 55, 64, 81, 104, 113, 92, 226 | 49, 64, 78, 87, 103, 121, 120, 101, 227 | 72, 92, 95, 98, 112, 100, 103, 99 228 | }, 229 | true 230 | }; 231 | 232 | const QuantizationTable qTableCbCr50 = { 233 | { 234 | 17, 18, 24, 47, 99, 99, 99, 99, 235 | 18, 21, 26, 66, 99, 99, 99, 99, 236 | 24, 26, 56, 99, 99, 99, 99, 99, 237 | 47, 66, 99, 99, 99, 99, 99, 99, 238 | 99, 99, 99, 99, 99, 99, 99, 99, 239 | 99, 99, 99, 99, 99, 99, 99, 99, 240 | 99, 99, 99, 99, 99, 99, 99, 99, 241 | 99, 99, 99, 99, 99, 99, 99, 99 242 | }, 243 | true 244 | }; 245 | 246 | const QuantizationTable qTableY75 = { 247 | { 248 | 16/2, 11/2, 10/2, 16/2, 24/2, 40/2, 51/2, 61/2, 249 | 12/2, 12/2, 14/2, 19/2, 26/2, 58/2, 60/2, 55/2, 250 | 14/2, 13/2, 16/2, 24/2, 40/2, 57/2, 69/2, 56/2, 251 | 14/2, 17/2, 22/2, 29/2, 51/2, 87/2, 80/2, 62/2, 252 | 18/2, 22/2, 37/2, 56/2, 68/2, 109/2, 103/2, 77/2, 253 | 24/2, 35/2, 55/2, 64/2, 81/2, 104/2, 113/2, 92/2, 254 | 49/2, 64/2, 78/2, 87/2, 103/2, 121/2, 120/2, 101/2, 255 | 72/2, 92/2, 95/2, 98/2, 112/2, 100/2, 103/2, 99/2 256 | }, 257 | true 258 | }; 259 | 260 | const QuantizationTable qTableCbCr75 = { 261 | { 262 | 17/2, 18/2, 24/2, 47/2, 99/2, 99/2, 99/2, 99/2, 263 | 18/2, 21/2, 26/2, 66/2, 99/2, 99/2, 99/2, 99/2, 264 | 24/2, 26/2, 56/2, 99/2, 99/2, 99/2, 99/2, 99/2, 265 | 47/2, 66/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 266 | 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 267 | 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 268 | 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 269 | 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2, 99/2 270 | }, 271 | true 272 | }; 273 | 274 | const QuantizationTable qTableY100 = { 275 | { 276 | 1, 1, 1, 1, 1, 1, 1, 1, 277 | 1, 1, 1, 1, 1, 1, 1, 1, 278 | 1, 1, 1, 1, 1, 1, 1, 1, 279 | 1, 1, 1, 1, 1, 1, 1, 1, 280 | 1, 1, 1, 1, 1, 1, 1, 1, 281 | 1, 1, 1, 1, 1, 1, 1, 1, 282 | 1, 1, 1, 1, 1, 1, 1, 1, 283 | 1, 1, 1, 1, 1, 1, 1, 1 284 | }, 285 | true 286 | }; 287 | 288 | const QuantizationTable qTableCbCr100 = { 289 | { 290 | 1, 1, 1, 1, 1, 1, 1, 1, 291 | 1, 1, 1, 1, 1, 1, 1, 1, 292 | 1, 1, 1, 1, 1, 1, 1, 1, 293 | 1, 1, 1, 1, 1, 1, 1, 1, 294 | 1, 1, 1, 1, 1, 1, 1, 1, 295 | 1, 1, 1, 1, 1, 1, 1, 1, 296 | 1, 1, 1, 1, 1, 1, 1, 1, 297 | 1, 1, 1, 1, 1, 1, 1, 1 298 | }, 299 | true 300 | }; 301 | 302 | const QuantizationTable* const qTables50[] = { &qTableY50, &qTableCbCr50, &qTableCbCr50 }; 303 | const QuantizationTable* const qTables75[] = { &qTableY75, &qTableCbCr75, &qTableCbCr75 }; 304 | const QuantizationTable* const qTables100[] = { &qTableY100, &qTableCbCr100, &qTableCbCr100 }; 305 | 306 | HuffmanTable hDCTableY = { 307 | { 0, 0, 1, 6, 7, 8, 9, 10, 11, 12, 12, 12, 12, 12, 12, 12, 12 }, 308 | { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b }, 309 | {}, 310 | false 311 | }; 312 | 313 | HuffmanTable hDCTableCbCr = { 314 | { 0, 0, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 12, 12, 12, 12, 12 }, 315 | { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b }, 316 | {}, 317 | false 318 | }; 319 | 320 | HuffmanTable hACTableY = { 321 | { 0, 0, 2, 3, 6, 9, 11, 15, 18, 23, 28, 32, 36, 36, 36, 37, 162 }, 322 | { 323 | 0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 324 | 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07, 325 | 0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 326 | 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0, 327 | 0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 328 | 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28, 329 | 0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 330 | 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 331 | 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 332 | 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 333 | 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 334 | 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 335 | 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 336 | 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 337 | 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 338 | 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5, 339 | 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 340 | 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2, 341 | 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 342 | 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 343 | 0xf9, 0xfa 344 | }, 345 | {}, 346 | false 347 | }; 348 | 349 | HuffmanTable hACTableCbCr = { 350 | { 0, 0, 2, 3, 5, 9, 13, 16, 20, 27, 32, 36, 40, 40, 41, 43, 162 }, 351 | { 352 | 0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 353 | 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71, 354 | 0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 355 | 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0, 356 | 0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 357 | 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26, 358 | 0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 359 | 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 360 | 0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 361 | 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 362 | 0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 363 | 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 364 | 0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 365 | 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 366 | 0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 367 | 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 368 | 0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 369 | 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 370 | 0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 371 | 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8, 372 | 0xf9, 0xfa 373 | }, 374 | {}, 375 | false 376 | }; 377 | 378 | HuffmanTable* const dcTables[] = { &hDCTableY, &hDCTableCbCr, &hDCTableCbCr }; 379 | HuffmanTable* const acTables[] = { &hACTableY, &hACTableCbCr, &hACTableCbCr }; 380 | 381 | #endif 382 | --------------------------------------------------------------------------------