1/*
2 * Copyright (C) 2009 Torch Mobile, Inc. All rights reserved.
3 * Copyright (C) Research In Motion Limited 2009-2010. All rights reserved.
4 *
5 *  This library is free software; you can redistribute it and/or
6 *  modify it under the terms of the GNU Library General Public
7 *  License as published by the Free Software Foundation; either
8 *  version 2 of the License, or (at your option) any later version.
9 *
10 *  This library is distributed in the hope that it will be useful,
11 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13 *  Library General Public License for more details.
14 *
15 *  You should have received a copy of the GNU Library General Public License
16 *  along with this library; see the file COPYING.LIB.  If not, write to
17 *  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18 *  Boston, MA 02110-1301, USA.
19 */
20
21
22#include "config.h"
23#include "JPEGImageEncoder.h"
24
25#include "IntSize.h"
26// FIXME: jpeglib.h requires stdio.h to be included first for FILE
27#include <stdio.h>
28#include "jpeglib.h"
29#include <setjmp.h>
30
31namespace WebCore {
32
33class JPEGDestinationManager : public jpeg_destination_mgr {
34public:
35    explicit JPEGDestinationManager(Vector<char>& toDump)
36        : m_dump(toDump)
37    {
38        // Zero base class memory.
39        jpeg_destination_mgr* base = this;
40        memset(base, 0, sizeof(jpeg_destination_mgr));
41    }
42    Vector<char> m_buffer;
43    Vector<char>& m_dump;
44};
45
46class JPEGCompressErrorMgr : public jpeg_error_mgr {
47public:
48    JPEGCompressErrorMgr()
49    {
50        // Zero memory
51        memset(this, 0, sizeof(JPEGCompressErrorMgr));
52    }
53
54    jmp_buf m_setjmpBuffer;
55};
56
57static void jpegInitializeDestination(j_compress_ptr compressData)
58{
59    JPEGDestinationManager* dest = static_cast<JPEGDestinationManager*>(compressData->dest);
60    dest->m_buffer.resize(4096);
61    dest->next_output_byte = reinterpret_cast<JOCTET*>(dest->m_buffer.data());
62    dest->free_in_buffer = dest->m_buffer.size();
63}
64
65static boolean jpegEmptyOutputBuffer(j_compress_ptr compressData)
66{
67    JPEGDestinationManager* dest = static_cast<JPEGDestinationManager*>(compressData->dest);
68    dest->m_dump.append(dest->m_buffer.data(), dest->m_buffer.size());
69    dest->next_output_byte  = reinterpret_cast<JOCTET*>(dest->m_buffer.data());
70    dest->free_in_buffer    = dest->m_buffer.size();
71    return TRUE;
72}
73
74static void jpegTerminateDestination(j_compress_ptr compressData)
75{
76    JPEGDestinationManager* dest = static_cast<JPEGDestinationManager*>(compressData->dest);
77    dest->m_dump.append(dest->m_buffer.data(), dest->m_buffer.size() - dest->free_in_buffer);
78}
79
80static void jpegErrorExit(j_common_ptr compressData)
81{
82    JPEGCompressErrorMgr* err = static_cast<JPEGCompressErrorMgr*>(compressData->err);
83    longjmp(err->m_setjmpBuffer, -1);
84}
85
86bool compressRGBABigEndianToJPEG(unsigned char* rgbaBigEndianData, const IntSize& size, Vector<char>& jpegData, const double* quality)
87{
88    struct jpeg_compress_struct compressData;
89    JPEGCompressErrorMgr err;
90    compressData.err = jpeg_std_error(&err);
91    err.error_exit = jpegErrorExit;
92
93    jpeg_create_compress(&compressData);
94
95    JPEGDestinationManager dest(jpegData);
96    compressData.dest = &dest;
97    dest.init_destination = jpegInitializeDestination;
98    dest.empty_output_buffer = jpegEmptyOutputBuffer;
99    dest.term_destination = jpegTerminateDestination;
100
101    compressData.image_width = size.width();
102    compressData.image_height = size.height();
103    compressData.input_components = 3;
104    compressData.in_color_space = JCS_RGB;
105    jpeg_set_defaults(&compressData);
106    int compressionQuality = 65;
107    if (quality && *quality >= 0.0 && *quality <= 1.0)
108        compressionQuality = static_cast<int>(*quality * 100 + 0.5);
109    jpeg_set_quality(&compressData, compressionQuality, FALSE);
110
111    // rowBuffer must be defined here so that its destructor is always called even when "setjmp" catches an error.
112    Vector<JSAMPLE, 600 * 3> rowBuffer;
113
114    if (setjmp(err.m_setjmpBuffer))
115        return false;
116
117    jpeg_start_compress(&compressData, TRUE);
118    rowBuffer.resize(compressData.image_width * 3);
119
120    const unsigned char* pixel = rgbaBigEndianData;
121    const unsigned char* pixelEnd = pixel + compressData.image_width * compressData.image_height * 4;
122    while (pixel < pixelEnd) {
123        JSAMPLE* output = rowBuffer.data();
124        for (const unsigned char* rowEnd = pixel + compressData.image_width * 4; pixel < rowEnd;) {
125            *output++ = static_cast<JSAMPLE>(*pixel++ & 0xFF); // red
126            *output++ = static_cast<JSAMPLE>(*pixel++ & 0xFF); // green
127            *output++ = static_cast<JSAMPLE>(*pixel++ & 0xFF); // blue
128            ++pixel; // skip alpha
129        }
130        output = rowBuffer.data();
131        jpeg_write_scanlines(&compressData, &output, 1);
132    }
133
134    jpeg_finish_compress(&compressData);
135    return true;
136}
137
138} // namespace WebCore
139