A new way to specify YUVA planar data from SkCodec to SkImage_Lazy
Tunnels through SkImageGenerator as well.
The new SkCodec interface doesn't assume three 8 bit planes.
New SkYUVASpec more clearly defines chroma subsampling and siting of
the planes.
The intent is to use this for other YUVA APIs as well, in particular
SkImage factories in the future.
In this change we convert to the SkYUVASpec to SkYUVASizeInfo
and SkYUVAIndex[4] representation. But the intent is to use
the SkYUVASpec representation throughout the pipeline once
legacy APIs are removed.
orientation GM is replicated to test a variety of chroma
subsampling configs.
Bug: skia:10632
Change-Id: I3fad35752b87cac16c51b24824331f2ae7d458d3
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/309658
Commit-Queue: Brian Salomon <bsalomon@google.com>
Reviewed-by: Robert Phillips <robertphillips@google.com>
Reviewed-by: Leon Scroggins <scroggo@google.com>
diff --git a/src/codec/SkJpegCodec.cpp b/src/codec/SkJpegCodec.cpp
index 9aa7d2f..d856347 100644
--- a/src/codec/SkJpegCodec.cpp
+++ b/src/codec/SkJpegCodec.cpp
@@ -743,7 +743,10 @@
return (uint32_t) count == jpeg_skip_scanlines(fDecoderMgr->dinfo(), count);
}
-static bool is_yuv_supported(jpeg_decompress_struct* dinfo) {
+static bool is_yuv_supported(const jpeg_decompress_struct* dinfo,
+ SkYUVAInfo::PlanarConfig* planarConfig,
+ SkColorType colorTypes[SkYUVAInfo::kMaxPlanes],
+ size_t rowBytes[SkYUVAInfo::kMaxPlanes]) {
// Scaling is not supported in raw data mode.
SkASSERT(dinfo->scale_num == dinfo->scale_denom);
@@ -790,78 +793,98 @@
// cases?
int hSampY = dinfo->comp_info[0].h_samp_factor;
int vSampY = dinfo->comp_info[0].v_samp_factor;
- return (1 == hSampY && 1 == vSampY) ||
- (2 == hSampY && 1 == vSampY) ||
- (2 == hSampY && 2 == vSampY) ||
- (1 == hSampY && 2 == vSampY) ||
- (4 == hSampY && 1 == vSampY) ||
- (4 == hSampY && 2 == vSampY);
-}
+ SkASSERT(hSampY == dinfo->max_h_samp_factor);
+ SkASSERT(vSampY == dinfo->max_v_samp_factor);
-bool SkJpegCodec::onQueryYUV8(SkYUVASizeInfo* sizeInfo, SkYUVColorSpace* colorSpace) const {
- jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();
- if (!is_yuv_supported(dinfo)) {
+ SkYUVAInfo::PlanarConfig tempPlanarConfig;
+ if (1 == hSampY && 1 == vSampY) {
+ tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_444;
+ } else if (2 == hSampY && 1 == vSampY) {
+ tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_422;
+ } else if (2 == hSampY && 2 == vSampY) {
+ tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_420;
+ } else if (1 == hSampY && 2 == vSampY) {
+ tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_440;
+ } else if (4 == hSampY && 1 == vSampY) {
+ tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_411;
+ } else if (4 == hSampY && 2 == vSampY) {
+ tempPlanarConfig = SkYUVAInfo::PlanarConfig::kY_U_V_410;
+ } else {
return false;
}
-
- jpeg_component_info * comp_info = dinfo->comp_info;
- for (int i = 0; i < 3; ++i) {
- sizeInfo->fSizes[i].set(comp_info[i].downsampled_width, comp_info[i].downsampled_height);
- sizeInfo->fWidthBytes[i] = comp_info[i].width_in_blocks * DCTSIZE;
+ if (planarConfig) {
+ *planarConfig = tempPlanarConfig;
}
-
- // JPEG never has an alpha channel
- sizeInfo->fSizes[3].fHeight = sizeInfo->fSizes[3].fWidth = sizeInfo->fWidthBytes[3] = 0;
-
- sizeInfo->fOrigin = this->getOrigin();
-
- if (colorSpace) {
- *colorSpace = kJPEG_SkYUVColorSpace;
+ if (colorTypes) {
+ for (int i = 0; i < 3; ++i) {
+ colorTypes[i] = kAlpha_8_SkColorType;
+ }
}
-
+ if (rowBytes) {
+ for (int i = 0; i < 3; ++i) {
+ rowBytes[i] = dinfo->comp_info[i].width_in_blocks * DCTSIZE;
+ }
+ }
return true;
}
-SkCodec::Result SkJpegCodec::onGetYUV8Planes(const SkYUVASizeInfo& sizeInfo,
- void* planes[SkYUVASizeInfo::kMaxCount]) {
- SkYUVASizeInfo defaultInfo;
-
- // This will check is_yuv_supported(), so we don't need to here.
- bool supportsYUV = this->onQueryYUV8(&defaultInfo, nullptr);
- if (!supportsYUV ||
- sizeInfo.fSizes[0] != defaultInfo.fSizes[0] ||
- sizeInfo.fSizes[1] != defaultInfo.fSizes[1] ||
- sizeInfo.fSizes[2] != defaultInfo.fSizes[2] ||
- sizeInfo.fWidthBytes[0] < defaultInfo.fWidthBytes[0] ||
- sizeInfo.fWidthBytes[1] < defaultInfo.fWidthBytes[1] ||
- sizeInfo.fWidthBytes[2] < defaultInfo.fWidthBytes[2]) {
- return fDecoderMgr->returnFailure("onGetYUV8Planes", kInvalidInput);
+bool SkJpegCodec::onQueryYUVAInfo(SkYUVAInfo* yuvaInfo,
+ SkColorType colorTypes[SkYUVAInfo::kMaxPlanes],
+ size_t rowBytes[SkYUVAInfo::kMaxPlanes]) const {
+ jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();
+ SkYUVAInfo::PlanarConfig planarConfig;
+ if (!is_yuv_supported(dinfo, &planarConfig, colorTypes, rowBytes)) {
+ return false;
}
+ *yuvaInfo = SkYUVAInfo(this->dimensions(),
+ planarConfig,
+ kJPEG_Full_SkYUVColorSpace,
+ this->getOrigin(),
+ SkYUVAInfo::Siting::kCentered,
+ SkYUVAInfo::Siting::kCentered);
+ return true;
+}
+SkCodec::Result SkJpegCodec::onGetYUVAPlanes(const SkPixmap planes[SkYUVAInfo::kMaxPlanes]) {
+ // Get a pointer to the decompress info since we will use it quite frequently
+ jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();
+ if (!is_yuv_supported(dinfo, nullptr, nullptr, nullptr)) {
+ return fDecoderMgr->returnFailure("onGetYUVAPlanes", kInvalidInput);
+ }
// Set the jump location for libjpeg errors
skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr());
if (setjmp(jmp)) {
return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
}
- // Get a pointer to the decompress info since we will use it quite frequently
- jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();
-
dinfo->raw_data_out = TRUE;
if (!jpeg_start_decompress(dinfo)) {
return fDecoderMgr->returnFailure("startDecompress", kInvalidInput);
}
- // A previous implementation claims that the return value of is_yuv_supported()
- // may change after calling jpeg_start_decompress(). It looks to me like this
- // was caused by a bug in the old code, but we'll be safe and check here.
- SkASSERT(is_yuv_supported(dinfo));
-
- // Currently, we require that the Y plane dimensions match the image dimensions
- // and that the U and V planes are the same dimensions.
- SkASSERT(sizeInfo.fSizes[1] == sizeInfo.fSizes[2]);
- SkASSERT((uint32_t) sizeInfo.fSizes[0].width() == dinfo->output_width &&
- (uint32_t) sizeInfo.fSizes[0].height() == dinfo->output_height);
+#ifdef SK_DEBUG
+ {
+ // A previous implementation claims that the return value of is_yuv_supported()
+ // may change after calling jpeg_start_decompress(). It looks to me like this
+ // was caused by a bug in the old code, but we'll be safe and check here.
+ // Also check that pixmap properties agree with expectations.
+ SkYUVAInfo::PlanarConfig planeConfig;
+ SkColorType expectedColorTypes[SkYUVAInfo::kMaxPlanes];
+ size_t expectedRowbytes[SkYUVAInfo::kMaxPlanes];
+ SkASSERT(is_yuv_supported(dinfo, &planeConfig, expectedColorTypes, expectedRowbytes));
+ SkISize expectedDims[SkYUVAInfo::kMaxPlanes];
+ int n = SkYUVAInfo::ExpectedPlaneDims(planeConfig,
+ this->getOrigin(),
+ this->dimensions(),
+ expectedDims);
+ SkASSERT(n == 3);
+ for (int i = 0; i < n; ++i) {
+ SkASSERT(planes[i].colorType() == kAlpha_8_SkColorType);
+ SkASSERT(planes[i].dimensions() == expectedDims[i]);
+ SkASSERT(planes[i].rowBytes() == expectedRowbytes[i]);
+ }
+ }
+#endif
// Build a JSAMPIMAGE to handle output from libjpeg-turbo. A JSAMPIMAGE has
// a 2-D array of pixels for each of the components (Y, U, V) in the image.
@@ -871,26 +894,27 @@
// Set aside enough space for pointers to rows of Y, U, and V.
JSAMPROW rowptrs[2 * DCTSIZE + DCTSIZE + DCTSIZE];
- yuv[0] = &rowptrs[0]; // Y rows (DCTSIZE or 2 * DCTSIZE)
- yuv[1] = &rowptrs[2 * DCTSIZE]; // U rows (DCTSIZE)
- yuv[2] = &rowptrs[3 * DCTSIZE]; // V rows (DCTSIZE)
+ yuv[0] = &rowptrs[0]; // Y rows (DCTSIZE or 2 * DCTSIZE)
+ yuv[1] = &rowptrs[2 * DCTSIZE]; // U rows (DCTSIZE)
+ yuv[2] = &rowptrs[3 * DCTSIZE]; // V rows (DCTSIZE)
// Initialize rowptrs.
int numYRowsPerBlock = DCTSIZE * dinfo->comp_info[0].v_samp_factor;
+ static_assert(sizeof(JSAMPLE) == 1);
for (int i = 0; i < numYRowsPerBlock; i++) {
- rowptrs[i] = SkTAddOffset<JSAMPLE>(planes[0], i * sizeInfo.fWidthBytes[0]);
+ rowptrs[i] = static_cast<JSAMPLE*>(planes[0].writable_addr()) + i*planes[0].rowBytes();
}
for (int i = 0; i < DCTSIZE; i++) {
rowptrs[i + 2 * DCTSIZE] =
- SkTAddOffset<JSAMPLE>(planes[1], i * sizeInfo.fWidthBytes[1]);
+ static_cast<JSAMPLE*>(planes[1].writable_addr()) + i*planes[1].rowBytes();
rowptrs[i + 3 * DCTSIZE] =
- SkTAddOffset<JSAMPLE>(planes[2], i * sizeInfo.fWidthBytes[2]);
+ static_cast<JSAMPLE*>(planes[2].writable_addr()) + i*planes[2].rowBytes();
}
// After each loop iteration, we will increment pointers to Y, U, and V.
- size_t blockIncrementY = numYRowsPerBlock * sizeInfo.fWidthBytes[0];
- size_t blockIncrementU = DCTSIZE * sizeInfo.fWidthBytes[1];
- size_t blockIncrementV = DCTSIZE * sizeInfo.fWidthBytes[2];
+ size_t blockIncrementY = numYRowsPerBlock * planes[0].rowBytes();
+ size_t blockIncrementU = DCTSIZE * planes[1].rowBytes();
+ size_t blockIncrementV = DCTSIZE * planes[2].rowBytes();
uint32_t numRowsPerBlock = numYRowsPerBlock;
@@ -920,17 +944,17 @@
SkASSERT(dinfo->output_scanline == numIters * numRowsPerBlock);
if (remainingRows > 0) {
// libjpeg-turbo needs memory to be padded by the block sizes. We will fulfill
- // this requirement using a dummy row buffer.
+ // this requirement using an extra row buffer.
// FIXME: Should SkCodec have an extra memory buffer that can be shared among
// all of the implementations that use temporary/garbage memory?
- SkAutoTMalloc<JSAMPLE> dummyRow(sizeInfo.fWidthBytes[0]);
+ SkAutoTMalloc<JSAMPLE> extraRow(planes[0].rowBytes());
for (int i = remainingRows; i < numYRowsPerBlock; i++) {
- rowptrs[i] = dummyRow.get();
+ rowptrs[i] = extraRow.get();
}
int remainingUVRows = dinfo->comp_info[1].downsampled_height - DCTSIZE * numIters;
for (int i = remainingUVRows; i < DCTSIZE; i++) {
- rowptrs[i + 2 * DCTSIZE] = dummyRow.get();
- rowptrs[i + 3 * DCTSIZE] = dummyRow.get();
+ rowptrs[i + 2 * DCTSIZE] = extraRow.get();
+ rowptrs[i + 3 * DCTSIZE] = extraRow.get();
}
JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock);