bool JPEGImageDecoder::outputScanlines() { if (m_frameBufferCache.isEmpty()) return false; // Initialize the framebuffer if needed. ImageFrame& buffer = m_frameBufferCache[0]; if (buffer.status() == ImageFrame::FrameEmpty) { if (!buffer.setSize(scaledSize().width(), scaledSize().height())) return setFailed(); buffer.setStatus(ImageFrame::FramePartial); // The buffer is transparent outside the decoded area while the image is // loading. The completed image will be marked fully opaque in jpegComplete(). buffer.setHasAlpha(true); buffer.setColorProfile(m_colorProfile); // For JPEGs, the frame always fills the entire image. buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); } jpeg_decompress_struct* info = m_reader->info(); #if defined(TURBO_JPEG_RGB_SWIZZLE) if (!m_scaled && turboSwizzled(info->out_color_space)) { while (info->output_scanline < info->output_height) { unsigned char* row = reinterpret_cast<unsigned char*>(buffer.getAddr(0, info->output_scanline)); if (jpeg_read_scanlines(info, &row, 1) != 1) return false; #if USE(QCMSLIB) if (qcms_transform* transform = m_reader->colorTransform()) qcms_transform_data_type(transform, row, row, info->output_width, rgbOutputColorSpace() == JCS_EXT_BGRA ? QCMS_OUTPUT_BGRX : QCMS_OUTPUT_RGBX); #endif } return true; } #endif switch (info->out_color_space) { // The code inside outputScanlines<int, bool> will be executed // for each pixel, so we want to avoid any extra comparisons there. // That is why we use template and template specializations here so // the proper code will be generated at compile time. case JCS_RGB: return outputScanlines<JCS_RGB>(buffer); case JCS_CMYK: return outputScanlines<JCS_CMYK>(buffer); default: ASSERT_NOT_REACHED(); } return setFailed(); }
bool JPEGImageDecoder::outputScanlines() { if (m_frameBufferCache.isEmpty()) return false; jpeg_decompress_struct* info = m_reader->info(); // Initialize the framebuffer if needed. ImageFrame& buffer = m_frameBufferCache[0]; if (buffer.status() == ImageFrame::FrameEmpty) { ASSERT(info->output_width == static_cast<JDIMENSION>(m_decodedSize.width())); ASSERT(info->output_height == static_cast<JDIMENSION>(m_decodedSize.height())); if (!buffer.setSize(info->output_width, info->output_height)) return setFailed(); buffer.setStatus(ImageFrame::FramePartial); // The buffer is transparent outside the decoded area while the image is // loading. The completed image will be marked fully opaque in jpegComplete(). buffer.setHasAlpha(true); // For JPEGs, the frame always fills the entire image. buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); } #if defined(TURBO_JPEG_RGB_SWIZZLE) if (turboSwizzled(info->out_color_space)) { while (info->output_scanline < info->output_height) { unsigned char* row = reinterpret_cast<unsigned char*>(buffer.getAddr(0, info->output_scanline)); if (jpeg_read_scanlines(info, &row, 1) != 1) return false; #if USE(QCMSLIB) if (qcms_transform* transform = m_reader->colorTransform()) qcms_transform_data_type(transform, row, row, info->output_width, rgbOutputColorSpace() == JCS_EXT_BGRA ? QCMS_OUTPUT_BGRX : QCMS_OUTPUT_RGBX); #endif } buffer.setPixelsChanged(true); return true; } #endif switch (info->out_color_space) { case JCS_RGB: return outputRows<JCS_RGB>(m_reader.get(), buffer); case JCS_CMYK: return outputRows<JCS_CMYK>(m_reader.get(), buffer); default: ASSERT_NOT_REACHED(); } return setFailed(); }
bool decode(const SharedBuffer& data, bool 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; // Calculate and set decoded size. m_info.scale_num = m_decoder->desiredScaleNumerator(); m_info.scale_denom = scaleDenominator; jpeg_calc_output_dimensions(&m_info); m_decoder->setDecodedSize(m_info.output_width, m_info.output_height); m_decoder->setOrientation(readImageOrientation(info())); #if USE(QCMSLIB) // Allow color management of the decoded RGBA pixels if possible. if (!m_decoder->ignoresGammaAndColorProfile()) { ColorProfile colorProfile; readColorProfile(info(), colorProfile); createColorTransform(colorProfile, 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); if (onlySize) { // 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(); m_info.enable_2pass_quant = false; m_info.do_block_smoothing = true; // 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)(reinterpret_cast<j_common_ptr>(&m_info), JPOOL_IMAGE, m_info.output_width * 4, 1); // 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; }
inline bool turboSwizzled(J_COLOR_SPACE colorSpace) { return colorSpace == rgbOutputColorSpace(); }
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 JPEGImageDecoder::outputScanlines() { if (m_frameBufferCache.isEmpty()) return false; // Initialize the framebuffer if needed. ImageFrame& buffer = m_frameBufferCache[0]; if (buffer.status() == ImageFrame::FrameEmpty) { if (!buffer.setSize(scaledSize().width(), scaledSize().height())) return setFailed(); buffer.setStatus(ImageFrame::FramePartial); // The buffer is transparent outside the decoded area while the image is // loading. The completed image will be marked fully opaque in jpegComplete(). buffer.setHasAlpha(true); buffer.setColorProfile(m_colorProfile); // For JPEGs, the frame always fills the entire image. buffer.setOriginalFrameRect(IntRect(IntPoint(), size())); } jpeg_decompress_struct* info = m_reader->info(); #if !ENABLE(IMAGE_DECODER_DOWN_SAMPLING) && defined(TURBO_JPEG_RGB_SWIZZLE) if (turboSwizzled(info->out_color_space)) { ASSERT(!m_scaled); while (info->output_scanline < info->output_height) { unsigned char* row = reinterpret_cast<unsigned char*>(buffer.getAddr(0, info->output_scanline)); if (jpeg_read_scanlines(info, &row, 1) != 1) return false; #if USE(QCMSLIB) if (qcms_transform* transform = m_reader->colorTransform()) qcms_transform_data_type(transform, row, row, info->output_width, rgbOutputColorSpace() == JCS_EXT_BGRA ? QCMS_OUTPUT_BGRX : QCMS_OUTPUT_RGBX); #endif } return true; } #endif JSAMPARRAY samples = m_reader->samples(); while (info->output_scanline < info->output_height) { // jpeg_read_scanlines will increase the scanline counter, so we // save the scanline before calling it. int sourceY = info->output_scanline; /* Request one scanline. Returns 0 or 1 scanlines. */ if (jpeg_read_scanlines(info, samples, 1) != 1) return false; int destY = scaledY(sourceY); if (destY < 0) continue; #if USE(QCMSLIB) if (m_reader->colorTransform() && info->out_color_space == JCS_RGB) qcms_transform_data(m_reader->colorTransform(), *samples, *samples, info->output_width); #endif int width = m_scaled ? m_scaledColumns.size() : info->output_width; for (int x = 0; x < width; ++x) { JSAMPLE* jsample = *samples + (m_scaled ? m_scaledColumns[x] : x) * ((info->out_color_space == JCS_RGB) ? 3 : 4); if (info->out_color_space == JCS_RGB) buffer.setRGBA(x, destY, jsample[0], jsample[1], jsample[2], 0xFF); #if defined(TURBO_JPEG_RGB_SWIZZLE) else if (info->out_color_space == JCS_EXT_RGBA) buffer.setRGBA(x, destY, jsample[0], jsample[1], jsample[2], 0xFF); else if (info->out_color_space == JCS_EXT_BGRA) buffer.setRGBA(x, destY, jsample[2], jsample[1], jsample[0], 0xFF); #endif else if (info->out_color_space == JCS_CMYK) { // Source is 'Inverted CMYK', output is RGB. // See: http://www.easyrgb.com/math.php?MATH=M12#text12 // Or: http://www.ilkeratalay.com/colorspacesfaq.php#rgb // From CMYK to CMY: // X = X * (1 - K ) + K [for X = C, M, or Y] // Thus, from Inverted CMYK to CMY is: // X = (1-iX) * (1 - (1-iK)) + (1-iK) => 1 - iX*iK // From CMY (0..1) to RGB (0..1): // R = 1 - C => 1 - (1 - iC*iK) => iC*iK [G and B similar] unsigned k = jsample[3]; buffer.setRGBA(x, destY, jsample[0] * k / 255, jsample[1] * k / 255, jsample[2] * k / 255, 0xFF); } else { ASSERT_NOT_REACHED(); return setFailed(); } } } return true; }