Geant4 Cross Reference |
1 1 2 // G.Barrand: pure header version of toojpeg f 3 4 // /////////////////////////////////////////// 5 // toojpeg.cpp 6 // written by Stephan Brumme, 2018-2019 7 // see https://create.stephan-brumme.com/toojp 8 // 9 10 #include <cstddef> //size_t 11 12 // - the "official" specifications: https://ww 13 // - Wikipedia has a short description of the 14 // - the popular STB Image library includes Jo 15 // - the most readable JPEG book (from a devel 16 // used copies are really cheap nowadays and 17 // - much more detailled is Mitchell/Pennebake 18 // which contains the official JPEG standard 19 20 namespace tools { 21 namespace toojpeg { 22 // //////////////////////////////////////// 23 // data types 24 typedef unsigned char uint8_t; 25 typedef unsigned short uint16_t; 26 typedef short int16_t; 27 typedef int int32_t; // at least four bytes 28 29 // //////////////////////////////////////// 30 // constants 31 32 // quantization tables from JPEG Standard, Ann 33 const uint8_t DefaultQuantLuminance[8*8] = 34 { 16, 11, 10, 16, 24, 40, 51, 61, // there 35 12, 12, 14, 19, 26, 58, 60, 55, // e.g. 36 14, 13, 16, 24, 40, 57, 69, 56, // btw: 37 14, 17, 22, 29, 51, 87, 80, 62, 38 18, 22, 37, 56, 68,109,103, 77, 39 24, 35, 55, 64, 81,104,113, 92, 40 49, 64, 78, 87,103,121,120,101, 41 72, 92, 95, 98,112,100,103, 99 }; 42 const uint8_t DefaultQuantChrominance[8*8] = 43 { 17, 18, 24, 47, 99, 99, 99, 99, 44 18, 21, 26, 66, 99, 99, 99, 99, 45 24, 26, 56, 99, 99, 99, 99, 99, 46 47, 66, 99, 99, 99, 99, 99, 99, 47 99, 99, 99, 99, 99, 99, 99, 99, 48 99, 99, 99, 99, 99, 99, 99, 99, 49 99, 99, 99, 99, 99, 99, 99, 99, 50 99, 99, 99, 99, 99, 99, 99, 99 }; 51 52 // 8x8 blocks are processed in zig-zag order 53 // most encoders use a zig-zag "forward" table 54 // note: ZigZagInv[ZigZag[i]] = i 55 const uint8_t ZigZagInv[8*8] = 56 { 0, 1, 8,16, 9, 2, 3,10, // ZigZag[] = 57 17,24,32,25,18,11, 4, 5, // 58 12,19,26,33,40,48,41,34, // 59 27,20,13, 6, 7,14,21,28, // 60 35,42,49,56,57,50,43,36, // 61 29,22,15,23,30,37,44,51, // 62 58,59,52,45,38,31,39,46, // 63 53,60,61,54,47,55,62,63 }; // 64 65 // static Huffman code tables from JPEG standa 66 // - CodesPerBitsize tables define how many Hu 67 // e.g. DcLuminanceCodesPerBitsize[2] = 5 be 68 // - Values tables are a list of values ordere 69 // e.g. AcLuminanceValues => Huffman(0x01,0x 70 71 // Huffman definitions for first DC/AC tables 72 const uint8_t DcLuminanceCodesPerBitsize[16] 73 const uint8_t DcLuminanceValues [12] 74 const uint8_t AcLuminanceCodesPerBitsize[16] 75 const uint8_t AcLuminanceValues [162] 76 { 0x01,0x02,0x03,0x00,0x04,0x11,0x05,0x12, 77 0x23,0x42,0xB1,0xC1,0x15,0x52,0xD1,0xF0, 78 0x29,0x2A,0x34,0x35,0x36,0x37,0x38,0x39, 79 0x5A,0x63,0x64,0x65,0x66,0x67,0x68,0x69, 80 0x8A,0x92,0x93,0x94,0x95,0x96,0x97,0x98, 81 0xB7,0xB8,0xB9,0xBA,0xC2,0xC3,0xC4,0xC5, 82 0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9,0xEA, 83 // Huffman definitions for second DC/AC tables 84 const uint8_t DcChrominanceCodesPerBitsize[16] 85 const uint8_t DcChrominanceValues [12] 86 const uint8_t AcChrominanceCodesPerBitsize[16] 87 const uint8_t AcChrominanceValues [162] 88 { 0x00,0x01,0x02,0x03,0x11,0x04,0x05,0x21, 89 0xA1,0xB1,0xC1,0x09,0x23,0x33,0x52,0xF0, 90 0x27,0x28,0x29,0x2A,0x35,0x36,0x37,0x38, 91 0x59,0x5A,0x63,0x64,0x65,0x66,0x67,0x68, 92 0x88,0x89,0x8A,0x92,0x93,0x94,0x95,0x96, 93 0xB5,0xB6,0xB7,0xB8,0xB9,0xBA,0xC2,0xC3, 94 0xE2,0xE3,0xE4,0xE5,0xE6,0xE7,0xE8,0xE9, 95 const int16_t CodeWordLimit = 2048; // +/-2^11 96 97 // //////////////////////////////////////// 98 // structs 99 100 // represent a single Huffman code 101 struct BitCode 102 { 103 //BitCode() = default; // undefined state, m 104 BitCode():code(0),numBits(0) {} 105 BitCode(const BitCode& a_from):code(a_from.c 106 BitCode& operator=(const BitCode& a_from) { 107 code = a_from.code; 108 numBits = a_from.numBits; 109 return *this; 110 } 111 112 BitCode(uint16_t code_, uint8_t numBits_) 113 : code(code_), numBits(numBits_) {} 114 uint16_t code; // JPEG's Huffman codes 115 uint8_t numBits; // number of valid bits 116 }; 117 118 // wrapper for bit output operations 119 struct BitWriter 120 { 121 // user-supplied callback that writes/stores 122 WRITE_ONE_BYTE output; 123 void* tag; 124 // initialize writer 125 explicit BitWriter(WRITE_ONE_BYTE output_,vo 126 buffer.data = 0; 127 buffer.numBits = 0; 128 } 129 130 // store the most recently encoded bits that 131 struct BitBuffer 132 { 133 int32_t data /*= 0*/; // actually only 134 uint8_t numBits /*= 0*/; // number of vali 135 } buffer; 136 137 // write Huffman bits stored in BitCode, kee 138 BitWriter& operator<<(const BitCode& data) 139 { 140 // append the new bits to those bits lefto 141 buffer.numBits += data.numBits; 142 buffer.data <<= data.numBits; 143 buffer.data |= data.code; 144 145 // write all "full" bytes 146 while (buffer.numBits >= 8) 147 { 148 // extract highest 8 bits 149 buffer.numBits -= 8; 150 uint8_t oneByte = uint8_t(buffer.data >> 151 output(oneByte,tag); 152 153 if (oneByte == 0xFF) // 0xFF has a speci 154 output(0,tag); // therefore pa 155 156 // note: I don't clear those written bit 157 // if you really want to "clean up 158 //buffer.bits &= (1 << buffer.numBits) - 159 } 160 return *this; 161 } 162 163 // write all non-yet-written bits, fill gaps 164 void flush() 165 { 166 // at most seven set bits needed to "fill" 167 *this << BitCode(0x7F, 7); // I should set 168 } 169 170 // NOTE: all the following BitWriter functio 171 // write a single byte 172 BitWriter& operator<<(uint8_t oneByte) 173 { 174 output(oneByte,tag); 175 return *this; 176 } 177 178 // write an array of bytes 179 template <typename T, int Size> 180 BitWriter& operator<<(T (&manyBytes)[Size]) 181 { 182 //for (auto c : manyBytes) 183 // output(c); 184 for(size_t i=0;i<Size;i++) output(manyByte 185 return *this; 186 } 187 188 // start a new JFIF block 189 void addMarker(uint8_t id, uint16_t length) 190 { 191 output(0xFF,tag); output(id,tag); // I 192 output(uint8_t(length >> 8),tag); // lengt 193 output(uint8_t(length & 0xFF),tag); 194 } 195 }; 196 197 // //////////////////////////////////////// 198 // functions / templates 199 200 // same as std::min() 201 template <typename Number> 202 inline Number minimum(Number value, Number max 203 { 204 return value <= maximum ? value : maximum; 205 } 206 207 // restrict a value to the interval [minimum, 208 template <typename Number, typename Limit> 209 inline Number clamp(Number value, Limit minVal 210 { 211 if (value <= minValue) return minValue; // n 212 if (value >= maxValue) return maxValue; // n 213 return value; // v 214 } 215 216 // convert from RGB to YCbCr, constants are si 217 inline float rgb2y (float r, float g, float b) 218 inline float rgb2cb(float r, float g, float b) 219 inline float rgb2cr(float r, float g, float b) 220 221 // forward DCT computation "in one dimension" 222 inline void DCT(float block[8*8], uint8_t stri 223 { 224 const float SqrtHalfSqrt = 1.306562965f; // 225 const float InvSqrt = 0.707106781f; // 226 const float HalfSqrtSqrt = 0.382683432f; // 227 const float InvSqrtSqrt = 0.541196100f; // 228 229 // modify in-place 230 float& block0 = block[0 ]; 231 float& block1 = block[1 * stride]; 232 float& block2 = block[2 * stride]; 233 float& block3 = block[3 * stride]; 234 float& block4 = block[4 * stride]; 235 float& block5 = block[5 * stride]; 236 float& block6 = block[6 * stride]; 237 float& block7 = block[7 * stride]; 238 239 // based on https://dev.w3.org/Amaya/libjpeg 240 float add07 = block0 + block7; float sub07 = 241 float add16 = block1 + block6; float sub16 = 242 float add25 = block2 + block5; float sub25 = 243 float add34 = block3 + block4; float sub34 = 244 245 float add0347 = add07 + add34; float sub07_3 246 float add1256 = add16 + add25; float sub16_2 247 248 block0 = add0347 + add1256; block4 = add0347 249 250 float z1 = (sub16_25 + sub07_34) * InvSqrt; 251 block2 = sub07_34 + z1; block6 = sub07_34 - 252 253 float sub23_45 = sub25 + sub34; // tmp10 ("o 254 float sub12_56 = sub16 + sub25; // tmp11 255 float sub01_67 = sub16 + sub07; // tmp12 256 257 float z5 = (sub23_45 - sub01_67) * HalfSqrtS 258 float z2 = sub23_45 * InvSqrtSqrt + z5; 259 float z3 = sub12_56 * InvSqrt; 260 float z4 = sub01_67 * SqrtHalfSqrt + z5; 261 float z6 = sub07 + z3; // z11 ("phase 5") 262 float z7 = sub07 - z3; // z13 263 block1 = z6 + z4; block7 = z6 - z4; // "phas 264 block5 = z7 + z2; block3 = z7 - z2; 265 } 266 267 // run DCT, quantize and write Huffman bit cod 268 inline int16_t encodeBlock(BitWriter& writer, 269 const BitCode huffmanDC[25 270 { 271 // "linearize" the 8x8 block, treat it as a 272 float* block64 = (float*) block; 273 274 // DCT: rows 275 for (size_t offset = 0; offset < 8; offset++ 276 DCT(block64 + offset*8, 1); 277 // DCT: columns 278 for (size_t offset = 0; offset < 8; offset++ 279 DCT(block64 + offset*1, 8); 280 281 // scale 282 for (size_t i = 0; i < 8*8; i++) 283 block64[i] *= scaled[i]; 284 285 // encode DC (the first coefficient is the " 286 int DC = int(block64[0] + (block64[0] >= 0 ? 287 288 // quantize and zigzag the other 63 coeffici 289 size_t posNonZero = 0; // find last coeffici 290 int16_t quantized[8*8]; 291 for (size_t i = 1; i < 8*8; i++) // start at 292 { 293 float value = block64[ZigZagInv[i]]; 294 // round to nearest integer 295 quantized[i] = int(value + (value >= 0 ? + 296 // remember offset of last non-zero coeffi 297 if (quantized[i] != 0) 298 posNonZero = i; 299 } 300 301 // same "average color" as previous block ? 302 int diff = DC - lastDC; 303 if (diff == 0) 304 writer << huffmanDC[0x00]; // yes, write 305 else 306 { 307 const BitCode bits = codewords[diff]; // n 308 writer << huffmanDC[bits.numBits] << bits; 309 } 310 311 // encode ACs (quantized[1..63]) 312 size_t offset = 0; // upper 4 bits count the 313 for (size_t i = 1; i <= posNonZero; i++) // 314 { 315 // zeros are encoded in a special way 316 while (quantized[i] == 0) // found another 317 { 318 offset += 0x10; // add 1 to the upper 319 // split into blocks of at most 16 conse 320 if (offset > 0xF0) // remember, the coun 321 { 322 writer << huffmanAC[0xF0]; // 0xF0 is 323 offset = 0; 324 } 325 i++; 326 } 327 328 const BitCode encoded = codewords[quantize 329 // combine number of zeros with the number 330 writer << huffmanAC[offset + encoded.numBi 331 offset = 0; 332 } 333 334 // send end-of-block code (0x00), only neede 335 if (posNonZero < 8*8 - 1) // = 63 336 writer << huffmanAC[0x00]; 337 338 return DC; 339 } 340 341 // Jon's code includes the pre-generated Huffm 342 // I don't like these "magic constants" and co 343 inline void generateHuffmanTable(const uint8_t 344 { 345 // process all bitsizes 1 thru 16, no JPEG H 346 uint16_t huffmanCode = 0; 347 for (uint8_t numBits = 1; numBits <= 16; num 348 { 349 // ... and each code of these bitsizes 350 for (uint8_t i = 0; i < numCodes[numBits - 351 result[*values++] = BitCode(huffmanCode+ 352 353 // next Huffman code needs to be one bit w 354 huffmanCode <<= 1; 355 } 356 } 357 358 // -------------------- externally visible cod 359 360 // the only exported function ... 361 inline bool writeJpeg(WRITE_ONE_BYTE output, v 362 bool isRGB, unsigned char quali 363 { 364 // reject invalid pointers 365 if (output == 0/*nullptr*/ || pixels_ == 0/* 366 return false; 367 // check image format 368 if (width == 0 || height == 0) 369 return false; 370 371 // number of components 372 const uint16_t numComponents = isRGB ? 3 : 1 373 // note: if there is just one component (=gr 374 // thus everything related to chromina 375 // I still compute a few things, like 376 377 // grayscale images can't be downsampled (be 378 if (!isRGB) 379 downsample = false; 380 381 // wrapper for all output operations 382 BitWriter bitWriter(output,tag); 383 384 // //////////////////////////////////////// 385 // JFIF headers 386 const uint8_t HeaderJfif[2+2+16] = 387 { 0xFF,0xD8, // SOI marker (star 388 0xFF,0xE0, // JFIF APP0 tag 389 0,16, // length: 16 bytes 390 'J','F','I','F',0, // JFIF identifier, 391 1,1, // JFIF version 1.1 392 0, // no density units 393 0,1,0,1, // density: 1 pixel 394 0,0 }; // no thumbnail (si 395 bitWriter << HeaderJfif; 396 397 // //////////////////////////////////////// 398 // comment (optional) 399 if (comment != 0/*nullptr*/) 400 { 401 // look for zero terminator 402 uint16_t length = 0; // = strlen(comment); 403 while (comment[length] != 0) 404 length++; 405 406 // write COM marker 407 bitWriter.addMarker(0xFE, 2+length); // bl 408 // ... and write the comment itself 409 for (uint16_t i = 0; i < length; i++) 410 bitWriter << comment[i]; 411 } 412 413 // //////////////////////////////////////// 414 // adjust quantization tables to desired qua 415 416 // quality level must be in 1 ... 100 417 uint16_t quality = clamp<uint16_t>(quality_, 418 // convert to an internal JPEG quality facto 419 quality = quality < 50 ? 5000 / quality : 20 420 421 uint8_t quantLuminance [8*8]; 422 uint8_t quantChrominance[8*8]; 423 for (size_t i = 0; i < 8*8; i++) 424 { 425 int luminance = (DefaultQuantLuminance 426 int chrominance = (DefaultQuantChrominance 427 428 // clamp to 1..255 429 quantLuminance [i] = clamp(luminance, 1 430 quantChrominance[i] = clamp(chrominance, 1 431 } 432 433 // write quantization tables 434 bitWriter.addMarker(0xDB, 2 + (isRGB ? 2 : 1 435 436 437 bitWriter << 0x00 << quantLuminance; // 438 if (isRGB) 439 bitWriter << 0x01 << quantChrominance; // 440 441 // //////////////////////////////////////// 442 // write image infos (SOF0 - start of frame) 443 bitWriter.addMarker(0xC0, 2+6+3*numComponent 444 445 // 8 bits per channel 446 bitWriter << 0x08 447 // image dimensions (big-endian) 448 << (height >> 8) << (height & 0xFF 449 << (width >> 8) << (width & 0xFF 450 451 // sampling and quantization tables for each 452 bitWriter << numComponents; // 1 compo 453 for (uint16_t id = 1; id <= numComponents; i 454 bitWriter << id // compone 455 // bitmasks for sampling: highest 4 bits: 456 << (id == 1 && downsample ? 0x22 457 << (id == 1 ? 0 : 1); // use qua 458 459 // //////////////////////////////////////// 460 // Huffman tables 461 // DHT marker - define Huffman tables 462 bitWriter.addMarker(0xC4, isRGB ? (2+208+208 463 // 2 bytes for the 464 // 1+16+12 for 465 // 1+16+162 for 466 // 1+16+12 for 467 // 1+16+162 for 468 469 // store luminance's DC+AC Huffman table def 470 bitWriter << 0x00 // highest 4 bits: 0 => DC 471 << DcLuminanceCodesPerBitsize 472 << DcLuminanceValues; 473 bitWriter << 0x10 // highest 4 bits: 1 => AC 474 << AcLuminanceCodesPerBitsize 475 << AcLuminanceValues; 476 477 // compute actual Huffman code tables (see J 478 BitCode huffmanLuminanceDC[256]; 479 BitCode huffmanLuminanceAC[256]; 480 generateHuffmanTable(DcLuminanceCodesPerBits 481 generateHuffmanTable(AcLuminanceCodesPerBits 482 483 // chrominance is only relevant for color im 484 BitCode huffmanChrominanceDC[256]; 485 BitCode huffmanChrominanceAC[256]; 486 if (isRGB) 487 { 488 // store luminance's DC+AC Huffman table d 489 bitWriter << 0x01 // highest 4 bits: 0 => 490 << DcChrominanceCodesPerBitsize 491 << DcChrominanceValues; 492 bitWriter << 0x11 // highest 4 bits: 1 => 493 << AcChrominanceCodesPerBitsize 494 << AcChrominanceValues; 495 496 // compute actual Huffman code tables (see 497 generateHuffmanTable(DcChrominanceCodesPer 498 generateHuffmanTable(AcChrominanceCodesPer 499 } 500 501 // //////////////////////////////////////// 502 // start of scan (there is only a single sca 503 bitWriter.addMarker(0xDA, 2+1+2*numComponent 504 505 506 // assign Huffman tables to each component 507 bitWriter << numComponents; 508 for (uint16_t id = 1; id <= numComponents; i 509 // highest 4 bits: DC Huffman table, lowes 510 bitWriter << id << (id == 1 ? 0x00 : 0x11) 511 512 // constant values for our baseline JPEGs (w 513 static const uint8_t Spectral[3] = { 0, 63, 514 bitWriter << Spectral; 515 516 // //////////////////////////////////////// 517 // adjust quantization tables with AAN scali 518 float scaledLuminance [8*8]; 519 float scaledChrominance[8*8]; 520 for (size_t i = 0; i < 8*8; i++) 521 { 522 size_t row = ZigZagInv[i] / 8; // same 523 size_t column = ZigZagInv[i] % 8; // same 524 525 // scaling constants for AAN DCT algorithm 526 static const float AanScaleFactors[8] = { 527 float factor = 1 / (AanScaleFactors[row] * 528 scaledLuminance [ZigZagInv[i]] = factor / 529 scaledChrominance[ZigZagInv[i]] = factor / 530 // if you really want JPEGs that are bitwi 531 //static const float aasf[] = { 1.0f * 2.8 532 //scaledLuminance [ZigZagInv[i]] = 1 / (q 533 //scaledChrominance[ZigZagInv[i]] = 1 / (q 534 } 535 536 // //////////////////////////////////////// 537 // precompute JPEG codewords for quantized D 538 BitCode codewordsArray[2 * CodeWordLimit]; 539 BitCode* codewords = &codewordsArray[CodeWor 540 uint8_t numBits = 1; // each codeword has at 541 int32_t mask = 1; // mask is always 2^num 542 for (int16_t value = 1; value < CodeWordLimi 543 { 544 // numBits = position of highest set bit ( 545 // mask = (2^numBits) - 1 546 if (value > mask) // one more bit ? 547 { 548 numBits++; 549 mask = (mask << 1) | 1; // append a set 550 } 551 codewords[-value] = BitCode(mask - value, 552 codewords[+value] = BitCode( value, 553 } 554 555 // just convert image data from void* 556 const uint8_t* pixels = (const uint8_t*)pixe 557 558 // the next two variables are frequently use 559 const unsigned short maxWidth = width - 1; 560 const unsigned short maxHeight = height - 1; 561 562 // process MCUs (minimum codes units) => ima 563 const unsigned short sampling = downsample ? 564 const unsigned short mcuSize = 8 * sampling 565 566 // average color of the previous MCU 567 int16_t lastYDC = 0, lastCbDC = 0, lastCrDC 568 // convert from RGB to YCbCr 569 float Y[8][8], Cb[8][8], Cr[8][8]; 570 571 for (unsigned short mcuY = 0; mcuY < height; 572 for (unsigned short mcuX = 0; mcuX < width 573 { 574 // YCbCr 4:4:4 format: each MCU is a 8x8 575 // YCbCr 4:2:0 format: each MCU represen 576 for (unsigned short blockY = 0; blockY < 577 for (unsigned short blockX = 0; blockX 578 { 579 // now we finally have an 8x8 block 580 for (unsigned short deltaY = 0; delt 581 { 582 size_t column = minimum(uint16_t(m 583 size_t row = minimum(uint16_t(m 584 for (size_t deltaX = 0; deltaX < 8 585 { 586 // find actual pixel position wi 587 size_t pixelPos = row * int(widt 588 if (column < maxWidth) 589 column++; 590 591 // grayscale images have solely 592 if (!isRGB) 593 { 594 Y[deltaY][deltaX] = pixels[pix 595 continue; 596 } 597 598 // RGB: 3 bytes per pixel (where 599 uint8_t r = pixels[3 * pixelPos 600 uint8_t g = pixels[3 * pixelPos 601 uint8_t b = pixels[3 * pixelPos 602 603 Y [deltaY][deltaX] = rgb2y (r, 604 // YCbCr444 is easy - the more c 605 if (!downsample) 606 { 607 Cb[deltaY][deltaX] = rgb2cb(r, 608 Cr[deltaY][deltaX] = rgb2cr(r, 609 } 610 } 611 } 612 613 // encode Y channel 614 lastYDC = encodeBlock(bitWriter, Y, sc 615 // Cb and Cr are encoded about 50 line 616 } 617 618 // grayscale images don't need any Cb an 619 if (!isRGB) 620 continue; 621 622 // ///////////////////////////////////// 623 // the following lines are only relevant 624 // average/downsample chrominance of fou 625 if (downsample) 626 for (short deltaY = 7; downsample && d 627 { 628 size_t row = minimum(uint16_t(m 629 size_t column = mcuX; 630 size_t pixelPos = (row * int(width) 631 632 // deltas (in bytes) to next row / c 633 size_t rowStep = (row < maxHei 634 size_t columnStep = (column < maxWid 635 636 for (short deltaX = 0; deltaX < 8; d 637 { 638 // let's add all four samples (2x2 639 size_t right = pixelPos + colu 640 size_t down = pixelPos + 641 size_t downRight = pixelPos + colu 642 643 // note: cast from 8 bits to >8 bi 644 short r = short(pixels[pixelPos 645 short g = short(pixels[pixelPos + 646 short b = short(pixels[pixelPos + 647 648 // convert to Cb and Cr 649 Cb[deltaY][deltaX] = rgb2cb(r, g, 650 Cr[deltaY][deltaX] = rgb2cr(r, g, 651 652 // step forward to next 2x2 area 653 pixelPos += 2*3; // 2 pixels => 6 654 column += 2; 655 656 // reached right border ? 657 if (column >= maxWidth) 658 { 659 columnStep = 0; 660 pixelPos = ((row + 1) * int(widt 661 } 662 } 663 } // end of YCbCr420 code for Cb and C 664 665 // encode Cb and Cr 666 lastCbDC = encodeBlock(bitWriter, Cb, sc 667 lastCrDC = encodeBlock(bitWriter, Cr, sc 668 } 669 670 bitWriter.flush(); // now image is completel 671 672 // /////////////////////////// 673 // EOI marker 674 bitWriter << 0xFF << 0xD9; // this marker ha 675 return true; 676 } // writeJpeg() 677 678 }} 679