summaryrefslogtreecommitdiffstats
path: root/khtml/misc/loader_jpeg.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'khtml/misc/loader_jpeg.cpp')
-rw-r--r--khtml/misc/loader_jpeg.cpp548
1 files changed, 548 insertions, 0 deletions
diff --git a/khtml/misc/loader_jpeg.cpp b/khtml/misc/loader_jpeg.cpp
new file mode 100644
index 000000000..4c9c97465
--- /dev/null
+++ b/khtml/misc/loader_jpeg.cpp
@@ -0,0 +1,548 @@
+/*
+ This file is part of the KDE libraries
+
+ Copyright (C) 2000 Dirk Mueller (mueller@kde.org)
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in
+ all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN
+ AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+ CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+*/
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#ifdef HAVE_LIBJPEG
+// on some systems, libjpeg installs its config.h file which causes a conflict
+// and makes the compiler barf with "XXX already defined".
+#ifdef HAVE_STDLIB_H
+#undef HAVE_STDLIB_H
+#endif
+#include "loader_jpeg.h"
+
+#include <stdio.h>
+#include <setjmp.h>
+#include <qdatetime.h>
+#include <kglobal.h>
+
+extern "C" {
+#define XMD_H
+#include <jpeglib.h>
+#undef const
+}
+
+
+#undef BUFFER_DEBUG
+//#define BUFFER_DEBUG
+
+#undef JPEG_DEBUG
+//#define JPEG_DEBUG
+
+// -----------------------------------------------------------------------------
+
+struct khtml_error_mgr : public jpeg_error_mgr {
+ jmp_buf setjmp_buffer;
+};
+
+extern "C" {
+
+ static
+ void khtml_error_exit (j_common_ptr cinfo)
+ {
+ khtml_error_mgr* myerr = (khtml_error_mgr*) cinfo->err;
+ char buffer[JMSG_LENGTH_MAX];
+ (*cinfo->err->format_message)(cinfo, buffer);
+#ifdef JPEG_DEBUG
+ qWarning("%s", buffer);
+#endif
+ longjmp(myerr->setjmp_buffer, 1);
+ }
+}
+
+static const int max_buf = 32768;
+static const int max_consumingtime = 2000;
+
+struct khtml_jpeg_source_mgr : public jpeg_source_mgr {
+ JOCTET buffer[max_buf];
+
+ int valid_buffer_len;
+ size_t skip_input_bytes;
+ int ateof;
+ QRect change_rect;
+ QRect old_change_rect;
+ QTime decoder_timestamp;
+ bool final_pass;
+ bool decoding_done;
+ bool do_progressive;
+
+public:
+ khtml_jpeg_source_mgr() KDE_NO_EXPORT;
+};
+
+
+extern "C" {
+
+ static
+ void khtml_j_decompress_dummy(j_decompress_ptr)
+ {
+ }
+
+ static
+ boolean khtml_fill_input_buffer(j_decompress_ptr cinfo)
+ {
+#ifdef BUFFER_DEBUG
+ qDebug("khtml_fill_input_buffer called!");
+#endif
+
+ khtml_jpeg_source_mgr* src = (khtml_jpeg_source_mgr*)cinfo->src;
+
+ if ( src->ateof )
+ {
+ /* Insert a fake EOI marker - as per jpeglib recommendation */
+ src->buffer[0] = (JOCTET) 0xFF;
+ src->buffer[1] = (JOCTET) JPEG_EOI;
+ src->bytes_in_buffer = 2;
+ src->next_input_byte = (JOCTET *) src->buffer;
+#ifdef BUFFER_DEBUG
+ qDebug("...returning true!");
+#endif
+ return true;
+ }
+ else
+ return false; /* I/O suspension mode */
+ }
+
+ static
+ void khtml_skip_input_data(j_decompress_ptr cinfo, long num_bytes)
+ {
+ if(num_bytes <= 0)
+ return; /* required noop */
+
+#ifdef BUFFER_DEBUG
+ qDebug("khtml_skip_input_data (%d) called!", num_bytes);
+#endif
+
+ khtml_jpeg_source_mgr* src = (khtml_jpeg_source_mgr*)cinfo->src;
+ src->skip_input_bytes += num_bytes;
+
+ unsigned int skipbytes = kMin(src->bytes_in_buffer, src->skip_input_bytes);
+
+#ifdef BUFFER_DEBUG
+ qDebug("skip_input_bytes is now %d", src->skip_input_bytes);
+ qDebug("skipbytes is now %d", skipbytes);
+ qDebug("valid_buffer_len is before %d", src->valid_buffer_len);
+ qDebug("bytes_in_buffer is %d", src->bytes_in_buffer);
+#endif
+
+ if(skipbytes < src->bytes_in_buffer)
+ memmove(src->buffer, src->next_input_byte+skipbytes, src->bytes_in_buffer - skipbytes);
+
+ src->bytes_in_buffer -= skipbytes;
+ src->valid_buffer_len = src->bytes_in_buffer;
+ src->skip_input_bytes -= skipbytes;
+
+ /* adjust data for jpeglib */
+ cinfo->src->next_input_byte = (JOCTET *) src->buffer;
+ cinfo->src->bytes_in_buffer = (size_t) src->valid_buffer_len;
+#ifdef BUFFER_DEBUG
+ qDebug("valid_buffer_len is afterwards %d", src->valid_buffer_len);
+ qDebug("skip_input_bytes is now %d", src->skip_input_bytes);
+#endif
+ }
+}
+
+
+khtml_jpeg_source_mgr::khtml_jpeg_source_mgr()
+{
+ jpeg_source_mgr::init_source = khtml_j_decompress_dummy;
+ jpeg_source_mgr::fill_input_buffer = khtml_fill_input_buffer;
+ jpeg_source_mgr::skip_input_data = khtml_skip_input_data;
+ jpeg_source_mgr::resync_to_restart = jpeg_resync_to_restart;
+ jpeg_source_mgr::term_source = khtml_j_decompress_dummy;
+ bytes_in_buffer = 0;
+ valid_buffer_len = 0;
+ skip_input_bytes = 0;
+ ateof = 0;
+ next_input_byte = buffer;
+ final_pass = false;
+ decoding_done = false;
+}
+
+
+
+// -----------------------------------------------------------------------------
+
+class KJPEGFormat : public QImageFormat
+{
+public:
+ KJPEGFormat();
+
+ virtual ~KJPEGFormat();
+
+ virtual int decode(QImage& img, QImageConsumer* consumer,
+ const uchar* buffer, int length);
+private:
+
+ enum {
+ Init,
+ readHeader,
+ startDecompress,
+ decompressStarted,
+ consumeInput,
+ prepareOutputScan,
+ doOutputScan,
+ readDone,
+ invalid
+ } state;
+
+ // structs for the jpeglib
+ struct jpeg_decompress_struct cinfo;
+ struct khtml_error_mgr jerr;
+ struct khtml_jpeg_source_mgr jsrc;
+};
+
+
+// -----------------------------------------------------------------------------
+
+KJPEGFormat::KJPEGFormat()
+{
+ memset(&cinfo, 0, sizeof(cinfo));
+ cinfo.err = jpeg_std_error(&jerr);
+ jpeg_create_decompress(&cinfo);
+ cinfo.err = jpeg_std_error(&jerr);
+ jerr.error_exit = khtml_error_exit;
+ cinfo.src = &jsrc;
+ state = Init;
+}
+
+
+KJPEGFormat::~KJPEGFormat()
+{
+ (void) jpeg_destroy_decompress(&cinfo);
+}
+
+/*
+ * return > 0 means "consumed x bytes, need more"
+ * return == 0 means "end of frame reached"
+ * return < 0 means "fatal error in image decoding, don't call me ever again"
+ */
+
+int KJPEGFormat::decode(QImage& image, QImageConsumer* consumer, const uchar* buffer, int length)
+{
+#ifdef JPEG_DEBUG
+ qDebug("KJPEGFormat::decode(%08lx, %08lx, %08lx, %d)",
+ &image, consumer, buffer, length);
+#endif
+
+ if(jsrc.ateof) {
+#ifdef JPEG_DEBUG
+ qDebug("ateof, eating");
+#endif
+ return length;
+ }
+
+
+ if(setjmp(jerr.setjmp_buffer))
+ {
+#ifdef JPEG_DEBUG
+ qDebug("jump into state invalid");
+#endif
+ if(consumer)
+ consumer->end();
+
+ // this is fatal
+ return -1;
+ }
+
+ int consumed = kMin(length, max_buf - jsrc.valid_buffer_len);
+
+#ifdef BUFFER_DEBUG
+ qDebug("consuming %d bytes", consumed);
+#endif
+
+ // filling buffer with the new data
+ memcpy(jsrc.buffer + jsrc.valid_buffer_len, buffer, consumed);
+ jsrc.valid_buffer_len += consumed;
+
+ if(jsrc.skip_input_bytes)
+ {
+#ifdef BUFFER_DEBUG
+ qDebug("doing skipping");
+ qDebug("valid_buffer_len %d", jsrc.valid_buffer_len);
+ qDebug("skip_input_bytes %d", jsrc.skip_input_bytes);
+#endif
+ int skipbytes = kMin((size_t) jsrc.valid_buffer_len, jsrc.skip_input_bytes);
+
+ if(skipbytes < jsrc.valid_buffer_len)
+ memmove(jsrc.buffer, jsrc.buffer+skipbytes, jsrc.valid_buffer_len - skipbytes);
+
+ jsrc.valid_buffer_len -= skipbytes;
+ jsrc.skip_input_bytes -= skipbytes;
+
+ // still more bytes to skip
+ if(jsrc.skip_input_bytes) {
+ if(consumed <= 0) qDebug("ERROR!!!");
+ return consumed;
+ }
+
+ }
+
+ cinfo.src->next_input_byte = (JOCTET *) jsrc.buffer;
+ cinfo.src->bytes_in_buffer = (size_t) jsrc.valid_buffer_len;
+
+#ifdef BUFFER_DEBUG
+ qDebug("buffer contains %d bytes", jsrc.valid_buffer_len);
+#endif
+
+ if(state == Init)
+ {
+ if(jpeg_read_header(&cinfo, true) != JPEG_SUSPENDED) {
+ // do some simple memory requirements limitations
+ // as long as we use that stupid Qt stuff
+ int s = cinfo.image_width * cinfo.image_height;
+ if ( s > 16384 * 12388 )
+ cinfo.scale_denom = 8;
+ else if ( s > 8192 * 6144 )
+ cinfo.scale_denom = 4;
+ else if ( s > 4096 * 3072 )
+ cinfo.scale_denom = 2;
+
+ if ( consumer )
+ consumer->setSize(cinfo.image_width/cinfo.scale_denom,
+ cinfo.image_height/cinfo.scale_denom);
+
+ state = startDecompress;
+ }
+ }
+
+ if(state == startDecompress)
+ {
+ jsrc.do_progressive = jpeg_has_multiple_scans( &cinfo );
+
+#ifdef JPEG_DEBUG
+ qDebug( "**** DOPROGRESSIVE: %d", jsrc.do_progressive );
+#endif
+ if ( jsrc.do_progressive )
+ cinfo.buffered_image = true;
+ else
+ cinfo.buffered_image = false;
+
+ // setup image sizes
+ jpeg_calc_output_dimensions( &cinfo );
+
+ if ( cinfo.jpeg_color_space == JCS_YCbCr )
+ cinfo.out_color_space = JCS_RGB;
+
+ cinfo.do_fancy_upsampling = true;
+ cinfo.do_block_smoothing = false;
+ cinfo.quantize_colors = false;
+
+ // false: IO suspension
+ if(jpeg_start_decompress(&cinfo)) {
+ if ( cinfo.output_components == 3 || cinfo.output_components == 4) {
+ image.create( cinfo.output_width, cinfo.output_height, 32 );
+ } else if ( cinfo.output_components == 1 ) {
+ image.create( cinfo.output_width, cinfo.output_height, 8, 256 );
+ for (int i=0; i<256; i++)
+ image.setColor(i, qRgb(i,i,i));
+ }
+
+#ifdef JPEG_DEBUG
+ qDebug("will create a picture %d/%d in size", cinfo.output_width, cinfo.output_height);
+#endif
+
+#ifdef JPEG_DEBUG
+ qDebug("ok, going to decompressStarted");
+#endif
+
+ jsrc.decoder_timestamp.start();
+ state = jsrc.do_progressive ? decompressStarted : doOutputScan;
+ }
+ }
+
+again:
+
+ if(state == decompressStarted) {
+ state = (!jsrc.final_pass && jsrc.decoder_timestamp.elapsed() < max_consumingtime)
+ ? consumeInput : prepareOutputScan;
+ }
+
+ if(state == consumeInput)
+ {
+ int retval;
+
+ do {
+ retval = jpeg_consume_input(&cinfo);
+ } while (retval != JPEG_SUSPENDED && retval != JPEG_REACHED_EOI
+ && (retval != JPEG_REACHED_SOS || jsrc.decoder_timestamp.elapsed() < max_consumingtime));
+
+ if(jsrc.decoder_timestamp.elapsed() >= max_consumingtime ||
+ jsrc.final_pass ||
+ retval == JPEG_REACHED_EOI || retval == JPEG_REACHED_SOS)
+ state = prepareOutputScan;
+ }
+
+ if(state == prepareOutputScan)
+ {
+ if ( jpeg_start_output(&cinfo, cinfo.input_scan_number) )
+ state = doOutputScan;
+ }
+
+ if(state == doOutputScan)
+ {
+ if(image.isNull() || jsrc.decoding_done)
+ {
+#ifdef JPEG_DEBUG
+ qDebug("complete in doOutputscan, eating..");
+#endif
+ return consumed;
+ }
+ uchar** lines = image.jumpTable();
+ int oldoutput_scanline = cinfo.output_scanline;
+
+ while(cinfo.output_scanline < cinfo.output_height &&
+ jpeg_read_scanlines(&cinfo, lines+cinfo.output_scanline, cinfo.output_height))
+ ; // here happens all the magic of decoding
+
+ int completed_scanlines = cinfo.output_scanline - oldoutput_scanline;
+#ifdef JPEG_DEBUG
+ qDebug("completed now %d scanlines", completed_scanlines);
+#endif
+
+ if ( cinfo.output_components == 3 ) {
+ // Expand 24->32 bpp.
+ for (int j=oldoutput_scanline; j<oldoutput_scanline+completed_scanlines; j++) {
+ uchar *in = image.scanLine(j) + cinfo.output_width * 3;
+ QRgb *out = (QRgb*)image.scanLine(j);
+
+ for (uint i=cinfo.output_width; i--; ) {
+ in-=3;
+ out[i] = qRgb(in[0], in[1], in[2]);
+ }
+ }
+ }
+
+ if(consumer && completed_scanlines)
+ {
+ QRect r(0, oldoutput_scanline, cinfo.output_width, completed_scanlines);
+#ifdef JPEG_DEBUG
+ qDebug("changing %d/%d %d/%d", r.x(), r.y(), r.width(), r.height());
+#endif
+ jsrc.change_rect |= r;
+
+ if ( jsrc.decoder_timestamp.elapsed() >= max_consumingtime ) {
+ if( !jsrc.old_change_rect.isEmpty()) {
+ consumer->changed(jsrc.old_change_rect);
+ jsrc.old_change_rect = QRect();
+ }
+ consumer->changed(jsrc.change_rect);
+ jsrc.change_rect = QRect();
+ jsrc.decoder_timestamp.restart();
+ }
+ }
+
+ if(cinfo.output_scanline >= cinfo.output_height)
+ {
+ if ( jsrc.do_progressive ) {
+ jpeg_finish_output(&cinfo);
+ jsrc.final_pass = jpeg_input_complete(&cinfo);
+ jsrc.decoding_done = jsrc.final_pass && cinfo.input_scan_number == cinfo.output_scan_number;
+ if ( !jsrc.decoding_done ) {
+ jsrc.old_change_rect |= jsrc.change_rect;
+ jsrc.change_rect = QRect();
+ }
+ }
+ else
+ jsrc.decoding_done = true;
+
+#ifdef JPEG_DEBUG
+ qDebug("one pass is completed, final_pass = %d, dec_done: %d, complete: %d",
+ jsrc.final_pass, jsrc.decoding_done, jpeg_input_complete(&cinfo));
+#endif
+ if(!jsrc.decoding_done)
+ {
+#ifdef JPEG_DEBUG
+ qDebug("starting another one, input_scan_number is %d/%d", cinfo.input_scan_number,
+ cinfo.output_scan_number);
+#endif
+ jsrc.decoder_timestamp.restart();
+ state = decompressStarted;
+ // don't return until necessary!
+ goto again;
+ }
+ }
+
+ if(state == doOutputScan && jsrc.decoding_done) {
+#ifdef JPEG_DEBUG
+ qDebug("input is complete, cleaning up, returning..");
+#endif
+ if ( consumer && !jsrc.change_rect.isEmpty() )
+ consumer->changed( jsrc.change_rect );
+
+ if(consumer)
+ consumer->end();
+
+ jsrc.ateof = true;
+
+ (void) jpeg_finish_decompress(&cinfo);
+ (void) jpeg_destroy_decompress(&cinfo);
+
+ state = readDone;
+
+ return 0;
+ }
+ }
+
+#ifdef BUFFER_DEBUG
+ qDebug("valid_buffer_len is now %d", jsrc.valid_buffer_len);
+ qDebug("bytes_in_buffer is now %d", jsrc.bytes_in_buffer);
+ qDebug("consumed %d bytes", consumed);
+#endif
+ if(jsrc.bytes_in_buffer && jsrc.buffer != jsrc.next_input_byte)
+ memmove(jsrc.buffer, jsrc.next_input_byte, jsrc.bytes_in_buffer);
+ jsrc.valid_buffer_len = jsrc.bytes_in_buffer;
+ return consumed;
+}
+
+// -----------------------------------------------------------------------------
+// This is the factory that teaches Qt about progressive JPEG's
+
+QImageFormat* khtml::KJPEGFormatType::decoderFor(const unsigned char* buffer, int length)
+{
+ if(length < 3) return 0;
+
+ if(buffer[0] == 0377 &&
+ buffer[1] == 0330 &&
+ buffer[2] == 0377)
+ return new KJPEGFormat;
+
+ return 0;
+}
+
+const char* khtml::KJPEGFormatType::formatName() const
+{
+ return "JPEG";
+}
+
+#else
+#ifdef __GNUC__
+#warning You don't seem to have libJPEG. jpeg support in khtml won't work
+#endif
+#endif // HAVE_LIBJPEG
+
+// -----------------------------------------------------------------------------
+