@@ -1076,6 +1076,56 @@ Task<void> postprocessRgb(
10761076 const int priority
10771077) {
10781078 const Vector2i size = resultData.size ();
1079+ const size_t numPixels = (size_t )size.x () * size.y ();
1080+
1081+ const size_t bps = photometric == PHOTOMETRIC_PALETTE ? 16 : dataBitsPerSample;
1082+
1083+ if (float * referenceBw; TIFFGetField (tif, TIFFTAG_REFERENCEBLACKWHITE, &referenceBw) && referenceBw) {
1084+ const size_t maxVal = (1ull << bps) - 1 ;
1085+
1086+ const bool isYCbCr = photometric == PHOTOMETRIC_YCBCR;
1087+ const Vector3f codingRange = {(float )maxVal, isYCbCr ? 127 .0f : (float )maxVal, isYCbCr ? 127 .0f : (float )maxVal};
1088+
1089+ const Vector3f refBlack = Vector3f{referenceBw[0 ], referenceBw[2 ], referenceBw[4 ]};
1090+ const Vector3f refWhite = Vector3f{referenceBw[1 ], referenceBw[3 ], referenceBw[5 ]};
1091+ const Vector3f invRange = 1 .0f / (refWhite - refBlack);
1092+
1093+ const Vector3f offset = isYCbCr ? Vector3f{0 .0f , 0 .5f , 0 .5f } : Vector3f{0 .0f };
1094+
1095+ const Vector3f totalScale = codingRange * invRange / maxVal;
1096+
1097+ tlog::debug () << fmt::format (" Found reference black/white: black={} white={}" , refBlack, refWhite);
1098+
1099+ co_await ThreadPool::global ().parallelForAsync <size_t >(
1100+ 0 ,
1101+ numPixels,
1102+ numPixels * numColorChannels,
1103+ [&](size_t i) {
1104+ for (int c = 0 ; c < numColorChannels; ++c) {
1105+ const size_t idx = i * numRgbaChannels + c;
1106+ floatRgbaData[idx] = (floatRgbaData[idx] * maxVal - refBlack[c]) * totalScale[c] + offset[c];
1107+ }
1108+ },
1109+ priority
1110+ );
1111+ }
1112+
1113+ if (photometric == PHOTOMETRIC_YCBCR && numRgbaChannels >= 3 ) {
1114+ Vector4f coeffs = {1 .402f , -0 .344136f , -0 .714136f , 1 .772f };
1115+ if (float * yCbCrCoeffs; TIFFGetField (tif, TIFFTAG_YCBCRCOEFFICIENTS, &yCbCrCoeffs) && yCbCrCoeffs) {
1116+ const Vector3f K = {yCbCrCoeffs[0 ], yCbCrCoeffs[1 ], yCbCrCoeffs[2 ]};
1117+ coeffs = {
1118+ 2 .0f * (1 .0f - K.x ()),
1119+ -2 .0f * K.z () * (1 .0f - K.z ()) / K.y (),
1120+ -2 .0f * K.x () * (1 .0f - K.x ()) / K.y (),
1121+ 2 .0f * (1 .0f - K.z ()),
1122+ };
1123+
1124+ tlog::debug () << fmt::format (" Found YCbCr coefficients: {} -> {}" , K, coeffs);
1125+ }
1126+
1127+ co_await yCbCrToRgb (floatRgbaData.data (), size, numRgbaChannels, priority, coeffs);
1128+ }
10791129
10801130 chroma_t chroma = rec709Chroma ();
10811131 if (float * primaries; TIFFGetField (tif, TIFFTAG_PRIMARYCHROMATICITIES, &primaries) && primaries) {
@@ -1114,7 +1164,6 @@ Task<void> postprocessRgb(
11141164 }
11151165 }
11161166
1117- const size_t bps = photometric == PHOTOMETRIC_PALETTE ? 16 : dataBitsPerSample;
11181167 const size_t maxIdx = (1ull << bps) - 1 ;
11191168
11201169 Vector3i transferRangeBlack = {0 };
@@ -1132,7 +1181,6 @@ Task<void> postprocessRgb(
11321181
11331182 const Vector3f scale = Vector3f (1 .0f ) / Vector3f (transferRangeWhite - transferRangeBlack);
11341183
1135- const size_t numPixels = (size_t )size.x () * size.y ();
11361184 co_await ThreadPool::global ().parallelForAsync <size_t >(
11371185 0 ,
11381186 numPixels,
@@ -1222,7 +1270,7 @@ Task<ImageData> decodeJpeg(
12221270) {
12231271 vector<uint8_t > stream;
12241272 if (jpegTables.size () > 4 ) {
1225- tlog::debug () << " JPEG tables found; prepending to compressed data..." ;
1273+ // tlog::debug() << "JPEG tables found; prepending to compressed data...";
12261274
12271275 const uint32_t tablesPayloadLen = jpegTables.size () - 4 ;
12281276 const uint8_t * tablesPayload = jpegTables.data () + 2 ;
@@ -1336,10 +1384,6 @@ Task<ImageData> decodeJpeg(
13361384 decompressGuard.disarm ();
13371385 jpeg_finish_decompress (&cinfo);
13381386
1339- if (photometric == PHOTOMETRIC_YCBCR && result.channels .size () >= 3 ) {
1340- co_await yCbCrToRgb (result.channels .front ().floatData (), result.channels .front ().size (), result.channels .size (), priority);
1341- }
1342-
13431387 *nestedBitsPerSample = (size_t )precision;
13441388 co_return result;
13451389}
@@ -1429,10 +1473,6 @@ Task<ImageData> readTiffImage(
14291473 if (decodeRaw) {
14301474 bitsPerSample = 32 ;
14311475 sampleFormat = SAMPLEFORMAT_IEEEFP;
1432-
1433- if (photometric == PHOTOMETRIC_YCBCR) {
1434- photometric = PHOTOMETRIC_RGB;
1435- }
14361476 }
14371477
14381478 span<const uint8_t > jpegTables = {};
@@ -1455,6 +1495,7 @@ Task<ImageData> readTiffImage(
14551495 PHOTOMETRIC_RGB,
14561496 PHOTOMETRIC_PALETTE,
14571497 PHOTOMETRIC_MASK,
1498+ PHOTOMETRIC_YCBCR,
14581499 PHOTOMETRIC_LOGLUV,
14591500 PHOTOMETRIC_LOGL,
14601501 PHOTOMETRIC_CFA, // Color Filter Array; displayed as grayscale for now
@@ -1469,7 +1510,26 @@ Task<ImageData> readTiffImage(
14691510 }
14701511
14711512 if (photometric == PHOTOMETRIC_YCBCR) {
1472- throw ImageLoadError{" YCbCr images are unsupported." };
1513+ if (compression == COMPRESSION_JPEG || compression == COMPRESSION_LOSSY_JPEG || compression == COMPRESSION_JP2000) {
1514+ // Our JPEG decoder upsamples YCbCr data for us
1515+ TIFFUnsetField (tif, TIFFTAG_YCBCRSUBSAMPLING);
1516+
1517+ if (compression == COMPRESSION_JP2000) {
1518+ // Our JPEG2000 encoder furthermore outputs RGB directly
1519+ photometric = PHOTOMETRIC_RGB;
1520+ TIFFUnsetField (tif, TIFFTAG_REFERENCEBLACKWHITE);
1521+ }
1522+ }
1523+
1524+ if (uint16_t subsampling[2 ]; TIFFGetField (tif, TIFFTAG_YCBCRSUBSAMPLING, &subsampling[0 ], &subsampling[1 ])) {
1525+ tlog::debug () << fmt::format (" Found YCbCr subsampling: {}x{}" , subsampling[0 ], subsampling[1 ]);
1526+
1527+ const bool hasSubsampling = subsampling[0 ] != 1 || subsampling[1 ] != 1 ;
1528+ if (hasSubsampling) {
1529+ // TODO: actually handle subsampling
1530+ throw ImageLoadError{" Subsampled YCbCr images are only supported for JPEG-compressed TIFFs." };
1531+ }
1532+ }
14731533 }
14741534
14751535 // TODO: handle CIELAB, ICCLAB, ITULAB (shouldn't be too tough)
@@ -2260,7 +2320,7 @@ Task<ImageData> readTiffImage(
22602320 } else if (photometric == PHOTOMETRIC_LOGLUV || photometric == PHOTOMETRIC_LOGL) {
22612321 // If we're a LogLUV image, we've already configured the encoder to give us linear XYZ data, so we can just convert that to Rec.709.
22622322 resultData.toRec709 = xyzToChromaMatrix (rec709Chroma ());
2263- } else if (photometric <= PHOTOMETRIC_PALETTE) {
2323+ } else if (photometric <= PHOTOMETRIC_PALETTE || photometric == PHOTOMETRIC_YCBCR ) {
22642324 co_await postprocessRgb (tif, photometric, dataBitsPerSample, numColorChannels, numRgbaChannels, floatRgbaData, resultData, priority);
22652325 } else {
22662326 // Other photometric interpretations do not need a transfer
0 commit comments