aboutsummaryrefslogtreecommitdiffstats
path: root/android/utils/jpeg-compress.c
blob: cd45bf5cfbd413d89998656a240fdb6780478dd1 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
/* Copyright (C) 2011 The Android Open Source Project
**
** This software is licensed under the terms of the GNU General Public
** License version 2, as published by the Free Software Foundation, and
** may be copied, distributed, and modified under those terms.
**
** This program is distributed in the hope that it will be useful,
** but WITHOUT ANY WARRANTY; without even the implied warranty of
** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
** GNU General Public License for more details.
*/

#include <stdint.h>
#include "jinclude.h"
#include "jpeglib.h"
#include "jpeg-compress.h"
#include "panic.h"

/* Implements JPEG destination manager's init_destination routine. */
static void _on_init_destination(j_compress_ptr cinfo);
/* Implements JPEG destination manager's empty_output_buffer routine. */
static boolean _on_empty_output_buffer(j_compress_ptr cinfo);
/* Implements JPEG destination manager's term_destination routine. */
static void _on_term_destination(j_compress_ptr cinfo);

/* JPEG compression descriptor. */
struct AJPEGDesc {
    /* Common JPEG compression destination manager header. */
    struct jpeg_destination_mgr     common;
    /* Buffer where to save compressed output. */
    uint8_t*                        jpeg_buf;
    /* Byte size of the 'jpeg_buf' */
    int                             size;
    /* Chunk size to increment the 'jpeg_buf' with on each allocation request. */
    int                             chunk_size;
    /* Size of the header to put in front of the compressed data. */
    int                             header_size;
};

/********************************************************************************
 *                      jpeglib callbacks.
 *******************************************************************************/

/* Implements JPEG destination manager's init_destination routine. */
static void
_on_init_destination(j_compress_ptr cinfo)
{
    AJPEGDesc* const dst = (AJPEGDesc*)cinfo->dest;
    if (dst->jpeg_buf == NULL) {
        /* This is the first time our destination manager is initialized.
         * Allocate minimal buffer. */
        dst->size = dst->chunk_size;
        dst->jpeg_buf = malloc(dst->size);
        if (dst->jpeg_buf == NULL) {
            APANIC("Unable to allocate %d bytes for JPEG compression", dst->size);
        }
    }
    /* Initialize common header with entire destination buffer. */
    dst->common.next_output_byte = dst->jpeg_buf + dst->header_size;
    dst->common.free_in_buffer = dst->size - dst->header_size;
}

/* Implements JPEG destination manager's empty_output_buffer routine.
 * Name is a bit misleading here. This routine is called by the compressor when
 * output buffer doesn't have enough free space to contain the next chunk of the
 * compressed data. So, here we should reallocate the output buffer, rather than
 * "empty" it.
 */
static boolean
_on_empty_output_buffer(j_compress_ptr cinfo)
{
    AJPEGDesc* const dst = (AJPEGDesc*)cinfo->dest;
    /* Save already compressed data size. */
    const int accumulated = jpeg_compressor_get_jpeg_size(dst);

    /* Reallocate output buffer. */
    dst->size += dst->chunk_size;
    dst->jpeg_buf = realloc(dst->jpeg_buf, dst->size);
    if (dst->jpeg_buf == NULL) {
        APANIC("Unable to allocate %d bytes for JPEG compression", dst->size);
    }

    /* Update common header. */
    dst->common.next_output_byte = dst->jpeg_buf + accumulated + dst->header_size;
    dst->common.free_in_buffer = dst->size - accumulated - dst->header_size;

    return TRUE;
}

/* Implements JPEG destination manager's term_destination routine.
 * We don't do anything here. All the cleanup will be performed when the user
 * calls jpeg_compressor_destroy. */
static void
_on_term_destination(j_compress_ptr cinfo)
{
}

/********************************************************************************
 *                      JPEG compressor API.
 *******************************************************************************/

AJPEGDesc*
jpeg_compressor_create(int header_size, int chunk_size)
{
    AJPEGDesc* dsc = (AJPEGDesc*)malloc(sizeof(AJPEGDesc));
    if (dsc == NULL) {
        APANIC("Unable to allocate JPEG compression descriptor.");
    }

    dsc->common.next_output_byte    = NULL;
    dsc->common.free_in_buffer      = 0;
    dsc->common.init_destination    = _on_init_destination;
    dsc->common.empty_output_buffer = _on_empty_output_buffer;
    dsc->common.term_destination    = _on_term_destination;
    dsc->jpeg_buf                   = NULL;
    dsc->size                       = 0;
    dsc->chunk_size                 = chunk_size;
    dsc->header_size                = header_size;
    return dsc;
}

void
jpeg_compressor_destroy(AJPEGDesc* dsc)
{
    if (dsc != NULL) {
        if (dsc->jpeg_buf != NULL) {
            free(dsc->jpeg_buf);
        }
        free(dsc);
    }
}

int
jpeg_compressor_get_jpeg_size(const AJPEGDesc* dsc)
{
    return (dsc->jpeg_buf == NULL) ? 0 :
        (uint8_t*)dsc->common.next_output_byte - dsc->jpeg_buf - dsc->header_size;
}

void*
jpeg_compressor_get_buffer(const AJPEGDesc* dsc)
{
     return dsc->jpeg_buf;
}

int
jpeg_compressor_get_header_size(const AJPEGDesc* dsc)
{
     return dsc->header_size;
}

void
jpeg_compressor_compress_fb(AJPEGDesc* dsc,
                            int x, int y, int w, int h, int num_lines,
                            int bpp, int bpl,
                            const uint8_t* fb,
                            int jpeg_quality,
                            int ydir){
    struct jpeg_compress_struct cinfo = {0};
    struct jpeg_error_mgr err_mgr;
    const int x_shift = x * bpp;

    /*
     * Initialize compressin information structure, and start compression
     */

    cinfo.err = jpeg_std_error(&err_mgr);
    jpeg_create_compress(&cinfo);
    cinfo.dest = &dsc->common;
    cinfo.image_width = w;
    cinfo.image_height = h;

    /* Decode framebuffer's pixel format. There can be only three:
     * - RGB565,
     * - RGBA8888,
     * - RGBX8888 */
    if (bpp == 2) {
        /* This is RGB565 - most commonly used pixel format for framebuffer. */
        cinfo.input_components = 2;
        cinfo.in_color_space = JCS_RGB_565;
    } else {
        /* RGBA8888, or RGBX8888 - makes no difference here. */
        cinfo.input_components = 4;
        cinfo.in_color_space = JCS_RGBA_8888;
    }
    jpeg_set_defaults(&cinfo);
    jpeg_set_quality(&cinfo, jpeg_quality, TRUE);
    jpeg_start_compress(&cinfo, TRUE);

    /* Line by line compress the region. */
    if (ydir >= 0) {
        while (cinfo.next_scanline < cinfo.image_height) {
            JSAMPROW rgb = (JSAMPROW)(fb + (cinfo.next_scanline + y) * bpl + x_shift);
            jpeg_write_scanlines(&cinfo, (JSAMPARRAY)&rgb, 1);
        }
    } else {
        const int y_shift = num_lines - y - 1;
        while (cinfo.next_scanline < cinfo.image_height) {
            JSAMPROW rgb = (JSAMPROW)(fb + (y_shift - cinfo.next_scanline) * bpl + x_shift);
            jpeg_write_scanlines(&cinfo, (JSAMPARRAY)&rgb, 1);
        }
    }

    /* Complete the compression. */
    jpeg_finish_compress(&cinfo);
    jpeg_destroy_compress(&cinfo);
}