void createColorTransform(const ColorProfile& colorProfile, bool hasAlpha, bool sRGB) { clearColorTransform(); if (colorProfile.isEmpty() && !sRGB) return; qcms_profile* deviceProfile = ImageDecoder::qcmsOutputDeviceProfile(); if (!deviceProfile) return; qcms_profile* inputProfile = 0; if (!colorProfile.isEmpty()) inputProfile = qcms_profile_from_memory(colorProfile.data(), colorProfile.size()); else inputProfile = qcms_profile_sRGB(); if (!inputProfile) return; // We currently only support color profiles for RGB and RGBA images. ASSERT(rgbData == qcms_profile_get_color_space(inputProfile)); if (qcms_profile_match(inputProfile, deviceProfile)) { qcms_profile_release(inputProfile); return; } // FIXME: Don't force perceptual intent if the image profile contains an intent. qcms_data_type dataFormat = hasAlpha ? QCMS_DATA_RGBA_8 : QCMS_DATA_RGB_8; m_transform = qcms_transform_create(inputProfile, dataFormat, deviceProfile, dataFormat, QCMS_INTENT_PERCEPTUAL); qcms_profile_release(inputProfile); }
static void getColorProfile(png_structp png, png_infop info, ColorProfile& colorProfile, bool& sRGB) { #ifdef PNG_iCCP_SUPPORTED ASSERT(colorProfile.isEmpty()); if (png_get_valid(png, info, PNG_INFO_sRGB)) { sRGB = true; return; } char* profileName; int compressionType; #if (PNG_LIBPNG_VER < 10500) png_charp profile; #else png_bytep profile; #endif png_uint_32 profileLength; if (!png_get_iCCP(png, info, &profileName, &compressionType, &profile, &profileLength)) return; // Only accept RGB color profiles from input class devices. bool ignoreProfile = false; char* profileData = reinterpret_cast<char*>(profile); if (profileLength < ImageDecoder::iccColorProfileHeaderLength) ignoreProfile = true; else if (!ImageDecoder::rgbColorProfile(profileData, profileLength)) ignoreProfile = true; else if (!ImageDecoder::inputDeviceColorProfile(profileData, profileLength)) ignoreProfile = true; if (!ignoreProfile) colorProfile.append(profileData, profileLength); #endif }
static void readColorProfile(jpeg_decompress_struct* info, ColorProfile& colorProfile) { #if USE(ICCJPEG) JOCTET* profile; unsigned profileLength; if (!read_icc_profile(info, &profile, &profileLength)) return; // Only accept RGB color profiles from input class devices. bool ignoreProfile = false; char* profileData = reinterpret_cast<char*>(profile); if (profileLength < ImageDecoder::iccColorProfileHeaderLength) ignoreProfile = true; else if (!ImageDecoder::rgbColorProfile(profileData, profileLength)) ignoreProfile = true; else if (!ImageDecoder::inputDeviceColorProfile(profileData, profileLength)) ignoreProfile = true; ASSERT(colorProfile.isEmpty()); if (!ignoreProfile) colorProfile.append(profileData, profileLength); free(profile); #endif }
void createColorTransform(const ColorProfile& colorProfile, bool hasAlpha) { if (m_transform) qcms_transform_release(m_transform); m_transform = 0; if (colorProfile.isEmpty()) return; qcms_profile* deviceProfile = ImageDecoder::qcmsOutputDeviceProfile(); if (!deviceProfile) return; qcms_profile* inputProfile = qcms_profile_from_memory(colorProfile.data(), colorProfile.size()); if (!inputProfile) return; // We currently only support color profiles for RGB profiled images. ASSERT(icSigRgbData == qcms_profile_get_color_space(inputProfile)); qcms_data_type dataFormat = hasAlpha ? QCMS_DATA_RGBA_8 : QCMS_DATA_RGB_8; // FIXME: Don't force perceptual intent if the image profile contains an intent. m_transform = qcms_transform_create(inputProfile, dataFormat, deviceProfile, dataFormat, QCMS_INTENT_PERCEPTUAL); qcms_profile_release(inputProfile); }
bool decode(const SharedBuffer& data, bool onlySize) { m_decodingSizeOnly = onlySize; unsigned newByteCount = data.size() - m_bufferLength; unsigned readOffset = m_bufferLength - m_info.src->bytes_in_buffer; m_info.src->bytes_in_buffer += newByteCount; m_info.src->next_input_byte = (JOCTET*)(data.data()) + readOffset; // If we still have bytes to skip, try to skip those now. if (m_bytesToSkip) skipBytes(m_bytesToSkip); m_bufferLength = data.size(); // We need to do the setjmp here. Otherwise bad things will happen if (setjmp(m_err.setjmp_buffer)) return m_decoder->setFailed(); switch (m_state) { case JPEG_HEADER: // Read file parameters with jpeg_read_header(). if (jpeg_read_header(&m_info, true) == JPEG_SUSPENDED) return false; // I/O suspension. switch (m_info.jpeg_color_space) { case JCS_GRAYSCALE: case JCS_RGB: case JCS_YCbCr: // libjpeg can convert GRAYSCALE and YCbCr image pixels to RGB. m_info.out_color_space = rgbOutputColorSpace(); break; case JCS_CMYK: case JCS_YCCK: // libjpeg can convert YCCK to CMYK, but neither to RGB, so we // manually convert CMKY to RGB. m_info.out_color_space = JCS_CMYK; break; default: return m_decoder->setFailed(); } // Don't allocate a giant and superfluous memory buffer when the // image is a sequential JPEG. m_info.buffered_image = jpeg_has_multiple_scans(&m_info); // Used to set up image size so arrays can be allocated. jpeg_calc_output_dimensions(&m_info); // Make a one-row-high sample array that will go away when done with // image. Always make it big enough to hold an RGB row. Since this // uses the IJG memory manager, it must be allocated before the call // to jpeg_start_compress(). m_samples = (*m_info.mem->alloc_sarray)((j_common_ptr) &m_info, JPOOL_IMAGE, m_info.output_width * 4, 1); m_state = JPEG_START_DECOMPRESS; // We can fill in the size now that the header is available. if (!m_decoder->setSize(m_info.image_width, m_info.image_height)) return false; // Allow color management of the decoded RGBA pixels if possible. if (!m_decoder->ignoresGammaAndColorProfile()) { ColorProfile rgbInputDeviceColorProfile = readColorProfile(info()); if (!rgbInputDeviceColorProfile.isEmpty()) m_decoder->setColorProfile(rgbInputDeviceColorProfile); } if (m_decodingSizeOnly) { // We can stop here. Reduce our buffer length and available // data. m_bufferLength -= m_info.src->bytes_in_buffer; m_info.src->bytes_in_buffer = 0; return true; } // FALL THROUGH case JPEG_START_DECOMPRESS: // Set parameters for decompression. // FIXME -- Should reset dct_method and dither mode for final pass // of progressive JPEG. m_info.dct_method = JDCT_ISLOW; m_info.dither_mode = JDITHER_FS; m_info.do_fancy_upsampling = true; m_info.enable_2pass_quant = false; m_info.do_block_smoothing = true; // Start decompressor. if (!jpeg_start_decompress(&m_info)) return false; // I/O suspension. // If this is a progressive JPEG ... m_state = (m_info.buffered_image) ? JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL; // FALL THROUGH case JPEG_DECOMPRESS_SEQUENTIAL: if (m_state == JPEG_DECOMPRESS_SEQUENTIAL) { if (!m_decoder->outputScanlines()) return false; // I/O suspension. // If we've completed image output... ASSERT(m_info.output_scanline == m_info.output_height); m_state = JPEG_DONE; } // FALL THROUGH case JPEG_DECOMPRESS_PROGRESSIVE: if (m_state == JPEG_DECOMPRESS_PROGRESSIVE) { int status; do { status = jpeg_consume_input(&m_info); } while ((status != JPEG_SUSPENDED) && (status != JPEG_REACHED_EOI)); for (;;) { if (!m_info.output_scanline) { int scan = m_info.input_scan_number; // If we haven't displayed anything yet // (output_scan_number == 0) and we have enough data for // a complete scan, force output of the last full scan. if (!m_info.output_scan_number && (scan > 1) && (status != JPEG_REACHED_EOI)) --scan; if (!jpeg_start_output(&m_info, scan)) return false; // I/O suspension. } if (m_info.output_scanline == 0xffffff) m_info.output_scanline = 0; if (!m_decoder->outputScanlines()) { if (!m_info.output_scanline) // Didn't manage to read any lines - flag so we // don't call jpeg_start_output() multiple times for // the same scan. m_info.output_scanline = 0xffffff; return false; // I/O suspension. } if (m_info.output_scanline == m_info.output_height) { if (!jpeg_finish_output(&m_info)) return false; // I/O suspension. if (jpeg_input_complete(&m_info) && (m_info.input_scan_number == m_info.output_scan_number)) break; m_info.output_scanline = 0; } } m_state = JPEG_DONE; } // FALL THROUGH case JPEG_DONE: // Finish decompression. return jpeg_finish_decompress(&m_info); case JPEG_ERROR: // We can get here if the constructor failed. return m_decoder->setFailed(); } return true; }
bool decode(const SharedBuffer& data, bool onlySize) { m_decodingSizeOnly = onlySize; unsigned newByteCount = data.size() - m_bufferLength; unsigned readOffset = m_bufferLength - m_info.src->bytes_in_buffer; m_info.src->bytes_in_buffer += newByteCount; m_info.src->next_input_byte = (JOCTET*)(data.data()) + readOffset; // If we still have bytes to skip, try to skip those now. if (m_bytesToSkip) skipBytes(m_bytesToSkip); m_bufferLength = data.size(); // We need to do the setjmp here. Otherwise bad things will happen if (setjmp(m_err.setjmp_buffer)) return m_decoder->setFailed(); switch (m_state) { case JPEG_HEADER: // Read file parameters with jpeg_read_header(). if (jpeg_read_header(&m_info, TRUE) == JPEG_SUSPENDED) return false; // I/O suspension. switch (m_info.jpeg_color_space) { case JCS_GRAYSCALE: case JCS_RGB: case JCS_YCbCr: // libjpeg can convert GRAYSCALE and YCbCr image pixels to RGB. m_info.out_color_space = rgbOutputColorSpace(); #if defined(TURBO_JPEG_RGB_SWIZZLE) if (m_info.saw_JFIF_marker) break; // FIXME: Swizzle decoding does not support Adobe transform=0 // images (yet), so revert to using JSC_RGB in that case. if (m_info.saw_Adobe_marker && !m_info.Adobe_transform) m_info.out_color_space = JCS_RGB; #endif break; case JCS_CMYK: case JCS_YCCK: // libjpeg can convert YCCK to CMYK, but neither to RGB, so we // manually convert CMKY to RGB. m_info.out_color_space = JCS_CMYK; break; default: return m_decoder->setFailed(); } m_state = JPEG_START_DECOMPRESS; // We can fill in the size now that the header is available. if (!m_decoder->setSize(m_info.image_width, m_info.image_height)) return false; m_decoder->setOrientation(readImageOrientation(info())); #if ENABLE(IMAGE_DECODER_DOWN_SAMPLING) && defined(TURBO_JPEG_RGB_SWIZZLE) // There's no point swizzle decoding if image down sampling will // be applied. Revert to using JSC_RGB in that case. if (m_decoder->willDownSample() && turboSwizzled(m_info.out_color_space)) m_info.out_color_space = JCS_RGB; #endif // Allow color management of the decoded RGBA pixels if possible. if (!m_decoder->ignoresGammaAndColorProfile()) { ColorProfile rgbInputDeviceColorProfile = readColorProfile(info()); if (!rgbInputDeviceColorProfile.isEmpty()) m_decoder->setColorProfile(rgbInputDeviceColorProfile); #if USE(QCMSLIB) createColorTransform(rgbInputDeviceColorProfile, colorSpaceHasAlpha(m_info.out_color_space)); #if defined(TURBO_JPEG_RGB_SWIZZLE) // Input RGBA data to qcms. Note: restored to BGRA on output. if (m_transform && m_info.out_color_space == JCS_EXT_BGRA) m_info.out_color_space = JCS_EXT_RGBA; #endif #endif } // Don't allocate a giant and superfluous memory buffer when the // image is a sequential JPEG. m_info.buffered_image = jpeg_has_multiple_scans(&m_info); // Used to set up image size so arrays can be allocated. jpeg_calc_output_dimensions(&m_info); // Make a one-row-high sample array that will go away when done with // image. Always make it big enough to hold an RGB row. Since this // uses the IJG memory manager, it must be allocated before the call // to jpeg_start_compress(). // FIXME: note that some output color spaces do not need the samples // buffer. Remove this allocation for those color spaces. m_samples = (*m_info.mem->alloc_sarray)((j_common_ptr) &m_info, JPOOL_IMAGE, m_info.output_width * 4, 1); if (m_decodingSizeOnly) { // We can stop here. Reduce our buffer length and available data. m_bufferLength -= m_info.src->bytes_in_buffer; m_info.src->bytes_in_buffer = 0; return true; } // FALL THROUGH case JPEG_START_DECOMPRESS: // Set parameters for decompression. // FIXME -- Should reset dct_method and dither mode for final pass // of progressive JPEG. m_info.dct_method = dctMethod(); m_info.dither_mode = ditherMode(); m_info.do_fancy_upsampling = doFancyUpsampling() ? TRUE : FALSE; m_info.enable_2pass_quant = FALSE; m_info.do_block_smoothing = TRUE; // Start decompressor. if (!jpeg_start_decompress(&m_info)) return false; // I/O suspension. // If this is a progressive JPEG ... m_state = (m_info.buffered_image) ? JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL; // FALL THROUGH case JPEG_DECOMPRESS_SEQUENTIAL: if (m_state == JPEG_DECOMPRESS_SEQUENTIAL) { if (!m_decoder->outputScanlines()) return false; // I/O suspension. // If we've completed image output... ASSERT(m_info.output_scanline == m_info.output_height); m_state = JPEG_DONE; } // FALL THROUGH case JPEG_DECOMPRESS_PROGRESSIVE: if (m_state == JPEG_DECOMPRESS_PROGRESSIVE) { int status; do { status = jpeg_consume_input(&m_info); } while ((status != JPEG_SUSPENDED) && (status != JPEG_REACHED_EOI)); for (;;) { if (!m_info.output_scanline) { int scan = m_info.input_scan_number; // If we haven't displayed anything yet // (output_scan_number == 0) and we have enough data for // a complete scan, force output of the last full scan. if (!m_info.output_scan_number && (scan > 1) && (status != JPEG_REACHED_EOI)) --scan; if (!jpeg_start_output(&m_info, scan)) return false; // I/O suspension. } if (m_info.output_scanline == 0xffffff) m_info.output_scanline = 0; // If outputScanlines() fails, it deletes |this|. Therefore, // copy the decoder pointer and use it to check for failure // to avoid member access in the failure case. JPEGImageDecoder* decoder = m_decoder; if (!decoder->outputScanlines()) { if (decoder->failed()) // Careful; |this| is deleted. return false; if (!m_info.output_scanline) // Didn't manage to read any lines - flag so we // don't call jpeg_start_output() multiple times for // the same scan. m_info.output_scanline = 0xffffff; return false; // I/O suspension. } if (m_info.output_scanline == m_info.output_height) { if (!jpeg_finish_output(&m_info)) return false; // I/O suspension. if (jpeg_input_complete(&m_info) && (m_info.input_scan_number == m_info.output_scan_number)) break; m_info.output_scanline = 0; } } m_state = JPEG_DONE; } // FALL THROUGH case JPEG_DONE: // Finish decompression. return jpeg_finish_decompress(&m_info); case JPEG_ERROR: // We can get here if the constructor failed. return m_decoder->setFailed(); } return true; }