// This warning triggers false postives way too often in here. #ifdefined(__GNUC__) && !defined(__clang__) #pragma GCC diagnostic ignored "-Wclobbered" #endif
SkCodec::Result SkJpegCodec::ReadHeader(
SkStream* stream,
SkCodec** codecOut,
JpegDecoderMgr** decoderMgrOut,
std::unique_ptr<SkEncodedInfo::ICCProfile> defaultColorProfile) { // Create a JpegDecoderMgr to own all of the decompress information
std::unique_ptr<JpegDecoderMgr> decoderMgr(new JpegDecoderMgr(stream));
// libjpeg errors will be caught and reported here
skjpeg_error_mgr::AutoPushJmpBuf jmp(decoderMgr->errorMgr()); if (setjmp(jmp)) { return decoderMgr->returnFailure("ReadHeader", kInvalidInput);
}
// Initialize the decompress info and the source manager
decoderMgr->init(); auto* dinfo = decoderMgr->dinfo();
// Instruct jpeg library to save the markers that we care about. Since // the orientation and color profile will not change, we can skip this // step on rewinds. if (codecOut) {
jpeg_save_markers(dinfo, kExifMarker, 0xFFFF);
jpeg_save_markers(dinfo, kICCMarker, 0xFFFF);
jpeg_save_markers(dinfo, kMpfMarker, 0xFFFF);
}
// Read the jpeg header switch (jpeg_read_header(dinfo, TRUE)) { case JPEG_HEADER_OK: break; case JPEG_SUSPENDED: return decoderMgr->returnFailure("ReadHeader", kIncompleteInput); default: return decoderMgr->returnFailure("ReadHeader", kInvalidInput);
}
if (codecOut) { // Get the encoded color type
SkEncodedInfo::Color color; if (!decoderMgr->getEncodedColor(&color)) { return kInvalidInput;
}
auto metadataDecoder =
std::make_unique<SkJpegMetadataDecoderImpl>(get_sk_marker_list(dinfo));
std::unique_ptr<SkCodec> SkJpegCodec::MakeFromStream(std::unique_ptr<SkStream> stream,
Result* result, std::unique_ptr<SkEncodedInfo::ICCProfile> defaultColorProfile) {
SkASSERT(result); if (!stream) {
*result = SkCodec::kInvalidInput; return nullptr;
}
SkCodec* codec = nullptr;
*result = ReadHeader(stream.get(), &codec, nullptr, std::move(defaultColorProfile)); if (kSuccess == *result) { // Codec has taken ownership of the stream, we do not need to delete it
SkASSERT(codec);
stream.release(); return std::unique_ptr<SkCodec>(codec);
} return nullptr;
}
/* * Return the row bytes of a particular image type and width
*/ static size_t get_row_bytes(const j_decompress_ptr dinfo) { const size_t colorBytes = (dinfo->out_color_space == JCS_RGB565) ? 2 :
dinfo->out_color_components; return dinfo->output_width * colorBytes;
}
/* * Calculate output dimensions based on the provided factors. * * Not to be used on the actual jpeg_decompress_struct used for decoding, since it will * incorrectly modify num_components.
*/ void calc_output_dimensions(jpeg_decompress_struct* dinfo, unsignedint num, unsignedint denom) {
dinfo->num_components = 0;
dinfo->scale_num = num;
dinfo->scale_denom = denom;
jpeg_calc_output_dimensions(dinfo);
}
/* * Return a valid set of output dimensions for this decoder, given an input scale
*/
SkISize SkJpegCodec::onGetScaledDimensions(float desiredScale) const { // libjpeg-turbo supports scaling by 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, and 1/1, so we will // support these as well unsignedint num; unsignedint denom = 8; if (desiredScale >= 0.9375) {
num = 8;
} elseif (desiredScale >= 0.8125) {
num = 7;
} elseif (desiredScale >= 0.6875f) {
num = 6;
} elseif (desiredScale >= 0.5625f) {
num = 5;
} elseif (desiredScale >= 0.4375f) {
num = 4;
} elseif (desiredScale >= 0.3125f) {
num = 3;
} elseif (desiredScale >= 0.1875f) {
num = 2;
} else {
num = 1;
}
// Set up a fake decompress struct in order to use libjpeg to calculate output dimensions
jpeg_decompress_struct dinfo;
sk_bzero(&dinfo, sizeof(dinfo));
dinfo.image_width = this->dimensions().width();
dinfo.image_height = this->dimensions().height();
dinfo.global_state = fReadyState;
calc_output_dimensions(&dinfo, num, denom);
// Return the calculated output dimensions for the given scale return SkISize::Make(dinfo.output_width, dinfo.output_height);
}
if (kUnknown_SkAlphaType == dstInfo.alphaType()) { returnfalse;
}
if (kOpaque_SkAlphaType != dstInfo.alphaType()) {
SkCodecPrintf("Warning: an opaque image should be decoded as opaque " "- it is being decoded as non-opaque, which will draw slower\n");
}
// Check for valid color types and set the output color space switch (dstInfo.colorType()) { case kRGBA_8888_SkColorType:
fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; break; case kBGRA_8888_SkColorType: if (needsColorXform) { // Always using RGBA as the input format for color xforms makes the // implementation a little simpler.
fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA;
} else {
fDecoderMgr->dinfo()->out_color_space = JCS_EXT_BGRA;
} break; case kRGB_565_SkColorType: if (needsColorXform) {
fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA;
} else {
fDecoderMgr->dinfo()->dither_mode = JDITHER_NONE;
fDecoderMgr->dinfo()->out_color_space = JCS_RGB565;
} break; case kGray_8_SkColorType: if (JCS_GRAYSCALE != encodedColorType) { returnfalse;
}
if (needsColorXform) {
fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA;
} else {
fDecoderMgr->dinfo()->out_color_space = JCS_GRAYSCALE;
} break; case kBGRA_10101010_XR_SkColorType: case kBGR_101010x_XR_SkColorType: case kRGBA_F16_SkColorType:
SkASSERT(needsColorXform);
fDecoderMgr->dinfo()->out_color_space = JCS_EXT_RGBA; break; default: returnfalse;
}
// Check if we will decode to CMYK. libjpeg-turbo does not convert CMYK to RGBA, so // we must do it ourselves. if (JCS_CMYK == encodedColorType || JCS_YCCK == encodedColorType) {
fDecoderMgr->dinfo()->out_color_space = JCS_CMYK;
}
returntrue;
}
/* * Checks if we can natively scale to the requested dimensions and natively scales the * dimensions if possible
*/ bool SkJpegCodec::onDimensionsSupported(const SkISize& size) {
skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); if (setjmp(jmp)) { return fDecoderMgr->returnFalse("onDimensionsSupported");
}
// Set up a fake decompress struct in order to use libjpeg to calculate output dimensions // FIXME: Why is this necessary?
jpeg_decompress_struct dinfo;
sk_bzero(&dinfo, sizeof(dinfo));
dinfo.image_width = this->dimensions().width();
dinfo.image_height = this->dimensions().height();
dinfo.global_state = fReadyState;
// libjpeg-turbo can scale to 1/8, 1/4, 3/8, 1/2, 5/8, 3/4, 7/8, and 1/1 unsignedint num = 8; constunsignedint denom = 8;
calc_output_dimensions(&dinfo, num, denom); while (dinfo.output_width != dstWidth || dinfo.output_height != dstHeight) {
// Return a failure if we have tried all of the possible scales if (1 == num || dstWidth > dinfo.output_width || dstHeight > dinfo.output_height) { returnfalse;
}
// Try the next scale
num -= 1;
calc_output_dimensions(&dinfo, num, denom);
}
int SkJpegCodec::readRows(const SkImageInfo& dstInfo, void* dst, size_t rowBytes, int count, const Options& opts) { // Set the jump location for libjpeg-turbo errors
skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); if (setjmp(jmp)) { return 0;
}
// When fSwizzleSrcRow is non-null, it means that we need to swizzle. In this case, // we will always decode into fSwizzlerSrcRow before swizzling into the next buffer. // We can never swizzle "in place" because the swizzler may perform sampling and/or // subsetting. // When fColorXformSrcRow is non-null, it means that we need to color xform and that // we cannot color xform "in place" (many times we can, but not when the src and dst // are different sizes). // In this case, we will color xform from fColorXformSrcRow into the dst.
JSAMPLE* decodeDst = (JSAMPLE*) dst;
uint32_t* swizzleDst = (uint32_t*) dst;
size_t decodeDstRowBytes = rowBytes;
size_t swizzleDstRowBytes = rowBytes; int dstWidth = opts.fSubset ? opts.fSubset->width() : dstInfo.width(); if (fSwizzleSrcRow && fColorXformSrcRow) {
decodeDst = (JSAMPLE*) fSwizzleSrcRow;
swizzleDst = fColorXformSrcRow;
decodeDstRowBytes = 0;
swizzleDstRowBytes = 0;
dstWidth = fSwizzler->swizzleWidth();
} elseif (fColorXformSrcRow) {
decodeDst = (JSAMPLE*) fColorXformSrcRow;
swizzleDst = fColorXformSrcRow;
decodeDstRowBytes = 0;
swizzleDstRowBytes = 0;
} elseif (fSwizzleSrcRow) {
decodeDst = (JSAMPLE*) fSwizzleSrcRow;
decodeDstRowBytes = 0;
dstWidth = fSwizzler->swizzleWidth();
}
for (int y = 0; y < count; y++) {
uint32_t lines = jpeg_read_scanlines(fDecoderMgr->dinfo(), &decodeDst, 1); if (0 == lines) { return y;
}
if (fSwizzler) {
fSwizzler->swizzle(swizzleDst, decodeDst);
}
/* * This is a bit tricky. We only need the swizzler to do format conversion if the jpeg is * encoded as CMYK. * And even then we still may not need it. If the jpeg has a CMYK color profile and a color * xform, the color xform will handle the CMYK->RGB conversion.
*/ staticinlinebool needs_swizzler_to_convert_from_cmyk(J_COLOR_SPACE jpegColorType, const skcms_ICCProfile* srcProfile, bool hasColorSpaceXform) { if (JCS_CMYK != jpegColorType) { returnfalse;
}
/* * Performs the jpeg decode
*/
SkCodec::Result SkJpegCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t dstRowBytes, const Options& options, int* rowsDecoded) { if (options.fSubset) { // Subsets are not supported. return kUnimplemented;
}
// Get a pointer to the decompress info since we will use it quite frequently
jpeg_decompress_struct* dinfo = fDecoderMgr->dinfo();
// Set the jump location for libjpeg errors
skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); if (setjmp(jmp)) { return fDecoderMgr->returnFailure("setjmp", kInvalidInput);
}
if (!jpeg_start_decompress(dinfo)) { return fDecoderMgr->returnFailure("startDecompress", kInvalidInput);
}
// The recommended output buffer height should always be 1 in high quality modes. // If it's not, we want to know because it means our strategy is not optimal.
SkASSERT(1 == dinfo->rec_outbuf_height);
if (needs_swizzler_to_convert_from_cmyk(dinfo->out_color_space,
this->getEncodedInfo().profile(), this->colorXform())) {
this->initializeSwizzler(dstInfo, options, true);
}
if (!this->allocateStorage(dstInfo)) { return kInternalError;
}
void SkJpegCodec::initializeSwizzler(const SkImageInfo& dstInfo, const Options& options, bool needsCMYKToRGB) {
Options swizzlerOptions = options; if (options.fSubset) { // Use fSwizzlerSubset if this is a subset decode. This is necessary in the case // where libjpeg-turbo provides a subset and then we need to subset it further. // Also, verify that fSwizzlerSubset is initialized and valid.
SkASSERT(!fSwizzlerSubset.isEmpty() && fSwizzlerSubset.x() <= options.fSubset->x() &&
fSwizzlerSubset.width() == options.fSubset->width());
swizzlerOptions.fSubset = &fSwizzlerSubset;
}
SkImageInfo swizzlerDstInfo = dstInfo; if (this->colorXform()) { // The color xform will be expecting RGBA 8888 input.
swizzlerDstInfo = swizzlerDstInfo.makeColorType(kRGBA_8888_SkColorType);
}
if (needsCMYKToRGB) { // The swizzler is used to convert to from CMYK. // The swizzler does not use the width or height on SkEncodedInfo. auto swizzlerInfo = SkEncodedInfo::Make(0, 0, SkEncodedInfo::kInvertedCMYK_Color,
SkEncodedInfo::kOpaque_Alpha, 8);
fSwizzler = SkSwizzler::Make(swizzlerInfo, nullptr, swizzlerDstInfo, swizzlerOptions);
} else { int srcBPP = 0; switch (fDecoderMgr->dinfo()->out_color_space) { case JCS_EXT_RGBA: case JCS_EXT_BGRA: case JCS_CMYK:
srcBPP = 4; break; case JCS_RGB565:
srcBPP = 2; break; case JCS_GRAYSCALE:
srcBPP = 1; break; default:
SkASSERT(false); break;
}
fSwizzler = SkSwizzler::MakeSimple(srcBPP, swizzlerDstInfo, swizzlerOptions);
}
SkASSERT(fSwizzler);
}
// libjpeg-turbo may need to align startX to a multiple of the IDCT // block size. If this is the case, it will decrease the value of // startX to the appropriate alignment and also increase the value // of width so that the right edge of the requested subset remains // the same.
jpeg_crop_scanline(fDecoderMgr->dinfo(), &startX, &width);
// Instruct the swizzler (if it is necessary) to further subset the // output provided by libjpeg-turbo. // // We set this here (rather than in the if statement below), so that // if (1) we don't need a swizzler for the subset, and (2) we need a // swizzler for CMYK, the swizzler will still use the proper subset // dimensions. // // Note that the swizzler will ignore the y and height parameters of // the subset. Since the scanline decoder (and the swizzler) handle // one row at a time, only the subsetting in the x-dimension matters.
fSwizzlerSubset.setXYWH(options.fSubset->x() - startX, 0,
options.fSubset->width(), options.fSubset->height());
// We will need a swizzler if libjpeg-turbo cannot provide the exact // subset that we request. if (startX != (uint32_t) options.fSubset->x() ||
width != (uint32_t) options.fSubset->width()) {
this->initializeSwizzler(dstInfo, options, needsCMYKToRGB);
}
}
// Make sure we have a swizzler if we are converting from CMYK. if (!fSwizzler && needsCMYKToRGB) {
this->initializeSwizzler(dstInfo, options, true);
}
if (!this->allocateStorage(dstInfo)) { return kInternalError;
}
return kSuccess;
}
int SkJpegCodec::onGetScanlines(void* dst, int count, size_t dstRowBytes) { int rows = this->readRows(this->dstInfo(), dst, dstRowBytes, count, this->options()); if (rows < count) { // This allows us to skip calling jpeg_finish_decompress().
fDecoderMgr->dinfo()->output_scanline = this->dstInfo().height();
}
return rows;
}
bool SkJpegCodec::onSkipScanlines(int count) { // Set the jump location for libjpeg errors
skjpeg_error_mgr::AutoPushJmpBuf jmp(fDecoderMgr->errorMgr()); if (setjmp(jmp)) { return fDecoderMgr->returnFalse("onSkipScanlines");
}
staticbool is_yuv_supported(const jpeg_decompress_struct* dinfo, const SkJpegCodec& codec, const SkYUVAPixmapInfo::SupportedDataTypes* supportedDataTypes,
SkYUVAPixmapInfo* yuvaPixmapInfo) { // Scaling is not supported in raw data mode.
SkASSERT(dinfo->scale_num == dinfo->scale_denom);
// I can't imagine that this would ever change, but we do depend on it.
static_assert(8 == DCTSIZE, "DCTSIZE (defined in jpeg library) should always be 8.");
if (JCS_YCbCr != dinfo->jpeg_color_space) { returnfalse;
}
// It is possible to perform a YUV decode for any combination of // horizontal and vertical sampling that is supported by // libjpeg/libjpeg-turbo. However, we will start by supporting only the // common cases (where U and V have samp_factors of one). // // The definition of samp_factor is kind of the opposite of what SkCodec // thinks of as a sampling factor. samp_factor is essentially a // multiplier, and the larger the samp_factor is, the more samples that // there will be. Ex: // U_plane_width = image_width * (U_h_samp_factor / max_h_samp_factor) // // Supporting cases where the samp_factors for U or V were larger than // that of Y would be an extremely difficult change, given that clients // allocate memory as if the size of the Y plane is always the size of the // image. However, this case is very, very rare. if ((1 != dinfo->comp_info[1].h_samp_factor) ||
(1 != dinfo->comp_info[1].v_samp_factor) ||
(1 != dinfo->comp_info[2].h_samp_factor) ||
(1 != dinfo->comp_info[2].v_samp_factor))
{ returnfalse;
}
// Support all common cases of Y samp_factors. // TODO (msarett): As mentioned above, it would be possible to support // more combinations of samp_factors. The issues are: // (1) Are there actually any images that are not covered // by these cases? // (2) How much complexity would be added to the // implementation in order to support these rare // cases? int hSampY = dinfo->comp_info[0].h_samp_factor; int vSampY = dinfo->comp_info[0].v_samp_factor;
SkASSERT(hSampY == dinfo->max_h_samp_factor);
SkASSERT(vSampY == dinfo->max_v_samp_factor);
SkCodec::Result SkJpegCodec::onGetYUVAPlanes(const SkYUVAPixmaps& yuvaPixmaps) { // 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, *this, 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);
}
dinfo->raw_data_out = TRUE; if (!jpeg_start_decompress(dinfo)) { return fDecoderMgr->returnFailure("startDecompress", kInvalidInput);
}
#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.
SkYUVAPixmapInfo info;
SkASSERT(is_yuv_supported(dinfo, *this, nullptr, &info));
SkASSERT(info.yuvaInfo() == yuvaPixmaps.yuvaInfo()); for (int i = 0; i < info.numPlanes(); ++i) {
SkASSERT(planes[i].colorType() == kAlpha_8_SkColorType);
SkASSERT(info.planeInfo(i) == planes[i].info());
}
} #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. // Cheat Sheet: // JSAMPIMAGE == JSAMPLEARRAY* == JSAMPROW** == JSAMPLE***
JSAMPARRAY yuv[3];
// 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)
// 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] = static_cast<JSAMPLE*>(planes[0].writable_addr()) + i* planes[0].rowBytes();
} for (int i = 0; i < DCTSIZE; i++) {
rowptrs[i + 2 * DCTSIZE] = static_cast<JSAMPLE*>(planes[1].writable_addr()) + i* planes[1].rowBytes();
rowptrs[i + 3 * DCTSIZE] = 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 * planes[0].rowBytes();
size_t blockIncrementU = DCTSIZE * planes[1].rowBytes();
size_t blockIncrementV = DCTSIZE * planes[2].rowBytes();
uint32_t numRowsPerBlock = numYRowsPerBlock;
// We intentionally round down here, as this first loop will only handle // full block rows. As a special case at the end, we will handle any // remaining rows that do not make up a full block. constint numIters = dinfo->output_height / numRowsPerBlock; for (int i = 0; i < numIters; i++) {
JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); if (linesRead < numRowsPerBlock) { // FIXME: Handle incomplete YUV decodes without signalling an error. return kInvalidInput;
}
uint32_t remainingRows = dinfo->output_height - dinfo->output_scanline;
SkASSERT(remainingRows == dinfo->output_height % numRowsPerBlock);
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 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?
AutoTMalloc<JSAMPLE> extraRow(planes[0].rowBytes()); for (int i = remainingRows; i < numYRowsPerBlock; i++) {
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] = extraRow.get();
rowptrs[i + 3 * DCTSIZE] = extraRow.get();
}
JDIMENSION linesRead = jpeg_read_raw_data(dinfo, yuv, numRowsPerBlock); if (linesRead < remainingRows) { // FIXME: Handle incomplete YUV decodes without signalling an error. return kInvalidInput;
}
}
return kSuccess;
}
bool SkJpegCodec::onGetGainmapCodec(SkGainmapInfo* info, std::unique_ptr<SkCodec>* gainmapCodec) {
std::unique_ptr<SkStream> stream; if (!this->onGetGainmapInfo(info, &stream)) { returnfalse;
} if (gainmapCodec) {
Result result;
*gainmapCodec = MakeFromStream(std::move(stream), &result); if (!*gainmapCodec) { returnfalse;
}
} returntrue;
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.