/* Appotech ax203 picframe JPEG-ish compression code * For ax203 picture frames with firmware version 3.5.x * * Copyright (c) 2010 Hans de Goede * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation; either version 2.1 of the License, or * (at your option) any later version. * * 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program; if not, write to the * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301 USA */ #include "config.h" #include #include #ifdef HAVE_LIBGD #include #endif #include "ax203.h" #ifdef HAVE_LIBJPEG #include "jpeg_memsrcdest.h" #endif #if defined(HAVE_LIBGD) && defined(HAVE_LIBJPEG) static int locate_tables_n_write(JOCTET *jpeg, int jpeg_size, JOCTET table_type, uint8_t *outbuf, int *outc) { int i, size, found, orig_outc = *outc; /* Reserve space for writing table size */ *outc += 2; /* Locate tables and write them to outbuf */ for (i = 2; i < jpeg_size; i += size) { if (jpeg[i] != 0xff) { gp_log (GP_LOG_ERROR, "ax203", "marker does not start with ff?"); return GP_ERROR_CORRUPTED_DATA; } if (jpeg[i + 1] == 0xda) break; found = jpeg[i + 1] == table_type; i += 2; size = ((jpeg[i] << 8) | jpeg[i + 1]) - 2; i += 2; if (found) { memcpy(outbuf + *outc, jpeg + i, size); *outc += size; } } size = *outc - orig_outc; outbuf[orig_outc ] = size >> 8; outbuf[orig_outc + 1] = size; return GP_OK; } static int copy_huffman(uint8_t *dst, JOCTET *src, int n) { int i, copied = 0; for (i = 0; i < n; i++) { /* Skip 0xff escaping 0x00's (ax203 special) */ if (i && src[i - 1] == 0xff && src[i] == 0x00) continue; dst[copied++] = src[i]; } return copied; } /* Store MCU info needed by the picture frame (probably so that it can start decompression with any block to allow transition effects) */ static void add_mcu_info(uint8_t *outbuf, int block_nr, int last_Y, int last_Cb, int last_Cr, int huffman_addr) { int info_addr = block_nr * 8; /* skip header */ huffman_addr -= 16; info_addr += 16; /* This is crazy, but it is what the pictframe wants */ huffman_addr -= 8 * block_nr; /* Store last DC vals + huffman data address */ outbuf[info_addr + 0] = last_Y; outbuf[info_addr + 1] = last_Y >> 8; outbuf[info_addr + 2] = last_Cb; outbuf[info_addr + 3] = last_Cb >> 8; outbuf[info_addr + 4] = last_Cr; outbuf[info_addr + 5] = last_Cr >> 8; outbuf[info_addr + 6] = huffman_addr; outbuf[info_addr + 7] = huffman_addr >> 8; } int ax206_compress_jpeg(Camera *camera, int **in, uint8_t *outbuf, int out_size, unsigned int width, unsigned int height) { struct jpeg_compress_struct cinfo; struct jpeg_decompress_struct dinfo; struct jpeg_error_mgr jcerr, jderr; JSAMPROW row_pointer[1]; JOCTET *buf = NULL, *regular_jpeg = NULL; jvirt_barray_ptr *in_coefficients; unsigned int i, x, y; int stop, size, ret, outc; unsigned long regular_jpeg_size = 0, buf_size = 0; int last_dc_val[3] = { 0, 0, 0 }; /* We have a rgb24bit image in the desired dimensions, first we compress it into a regular jpeg, which we use as a base for creating the ax203 JPEG-ish format */ cinfo.err = jpeg_std_error (&jcerr); jpeg_create_compress (&cinfo); jpeg_mem_dest (&cinfo, ®ular_jpeg, ®ular_jpeg_size); cinfo.image_width = width; cinfo.image_height = height; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; jpeg_set_defaults (&cinfo); cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 2; jpeg_start_compress (&cinfo, TRUE); while( cinfo.next_scanline < cinfo.image_height ) { JOCTET row[width * 3]; for (i = 0; i < width; i++) { int p = in[cinfo.next_scanline][i]; row[i * 3 + 0] = gdTrueColorGetRed(p); row[i * 3 + 1] = gdTrueColorGetGreen(p); row[i * 3 + 2] = gdTrueColorGetBlue(p); } row_pointer[0] = row; jpeg_write_scanlines (&cinfo, row_pointer, 1); } jpeg_finish_compress (&cinfo); jpeg_destroy_compress (&cinfo); /* Write image header to outbuf */ outbuf[0] = width >> 8; outbuf[1] = width; outbuf[2] = height >> 8; outbuf[3] = height; outbuf[4] = 3; /* 2x uv sub-sampling */ outbuf[5] = 0; outbuf[6] = 1; outbuf[7] = 1; outbuf[8] = 0; outbuf[9] = 1; outbuf[10] = 1; outbuf[11] = 0; outbuf[12] = 1; outbuf[13] = 1; outbuf[14] = 0; outbuf[15] = 0; /* Make outc point to after the MCU info table, so to the start of the quantisation tables */ outc = 16 + ((width + 15) / 16) * ((height + 15) / 16) * 8; /* Locate quant tables and write them to outbuf */ ret = locate_tables_n_write (regular_jpeg, regular_jpeg_size, 0xdb, outbuf, &outc); if (ret < 0) return ret; /* Locate huffman tables and write them to outbuf */ ret = locate_tables_n_write (regular_jpeg, regular_jpeg_size, 0xc4, outbuf, &outc); if (ret < 0) return ret; /* The ax203 has 2 perculiarities in the huffman data for the JPEG DCT coefficients: 1) It wants the MCU huffman data for each new MCU to start on a byte boundary 2) The component order in an MCU is Cb Cr Y, rather then Y Cb Cr We solve both these issues by decompressing the regular jpeg we've just created into its raw coefficients, following by creating mini JPEGs with the size of one MCU block, using the raw-coefficients from the regular JPEG we first created. This allows us to both shuffle the component order, and to byte align the start of each MCU. */ /* Get the raw coefficients from the regular JPEG */ dinfo.err = jpeg_std_error (&jderr); jpeg_create_decompress (&dinfo); jpeg_mem_src (&dinfo, regular_jpeg, regular_jpeg_size); jpeg_read_header (&dinfo, TRUE); in_coefficients = jpeg_read_coefficients (&dinfo); /* Create a JPEG compression object for our mini JPEGs */ cinfo.err = jpeg_std_error (&jcerr); jpeg_create_compress (&cinfo); cinfo.image_width = 16; cinfo.image_height = 16; cinfo.input_components = 3; cinfo.in_color_space = JCS_RGB; #if JPEG_LIB_VERSION >= 80 cinfo.min_DCT_h_scaled_size = dinfo.min_DCT_h_scaled_size; cinfo.min_DCT_v_scaled_size = dinfo.min_DCT_h_scaled_size; cinfo.jpeg_width = 16; cinfo.jpeg_height = 16; #endif jpeg_set_defaults (&cinfo); /* We will write Cb values as comp. 0, so give it chroma settings */ cinfo.comp_info[0].h_samp_factor = 1; cinfo.comp_info[0].v_samp_factor = 1; cinfo.comp_info[0].quant_tbl_no = 1; cinfo.comp_info[0].dc_tbl_no = 1; cinfo.comp_info[0].ac_tbl_no = 1; /* We will write Y values as comp. 2, so give it luma settings */ cinfo.comp_info[2].h_samp_factor = 2; cinfo.comp_info[2].v_samp_factor = 2; cinfo.comp_info[2].quant_tbl_no = 0; cinfo.comp_info[2].dc_tbl_no = 0; cinfo.comp_info[2].ac_tbl_no = 0; /* And create a mini JPEG for each MCU with the shuffled component order and extract its huffman data. This fixes our component order problem and gives us the needed byte aligning for free. */ for (y = 0; y < (height + 15) / 16; y++) { JBLOCKARRAY in_row[3], out_row[3]; jvirt_barray_ptr out_barray[3]; /* Notice the shuffling of components happening here !! */ in_row[0] = dinfo.mem->access_virt_barray((j_common_ptr)&dinfo, in_coefficients[1], y, 1, 0); in_row[1] = dinfo.mem->access_virt_barray((j_common_ptr)&dinfo, in_coefficients[2], y, 1, 0); in_row[2] = dinfo.mem->access_virt_barray((j_common_ptr)&dinfo, in_coefficients[0], y * 2, 2, 0); for (x = 0; x < (width + 15) / 16; x++) { /* (Re)init our destination buffer */ jpeg_mem_dest (&cinfo, &buf, &buf_size); /* Add MCU info block to output */ add_mcu_info (outbuf, y * ((width + 15) / 16) + x, last_dc_val[2], last_dc_val[0], last_dc_val[1], outc); /* Allocate virtual arrays to store the coefficients for the mini jpg we are making */ out_barray[0] = cinfo.mem->request_virt_barray( (j_common_ptr)&cinfo, JPOOL_IMAGE, 0, 1, 1, 1); out_barray[1] = cinfo.mem->request_virt_barray( (j_common_ptr)&cinfo, JPOOL_IMAGE, 0, 1, 1, 1); out_barray[2] = cinfo.mem->request_virt_barray( (j_common_ptr)&cinfo, JPOOL_IMAGE, 0, 2, 2, 2); jpeg_write_coefficients (&cinfo, out_barray); /* Copy over our 3 (or 6) coefficient blocks, and apply last_dc_val */ out_row[0] = cinfo.mem->access_virt_barray( (j_common_ptr)&cinfo, out_barray[0], 0, 1, 1); out_row[1] = cinfo.mem->access_virt_barray( (j_common_ptr)&cinfo, out_barray[1], 0, 1, 1); out_row[2] = cinfo.mem->access_virt_barray( (j_common_ptr)&cinfo, out_barray[2], 0, 2, 1); for (i = 0; i < 2; i++) { memcpy (out_row[i][0][0], in_row[i][0][x], sizeof(JBLOCK)); out_row[i][0][0][0] -= last_dc_val[i]; last_dc_val[i] = in_row[i][0][x][0]; } memcpy (out_row[2][0][0], in_row[2][0][x * 2 + 0], sizeof(JBLOCK)); memcpy (out_row[2][0][1], in_row[2][0][x * 2 + 1], sizeof(JBLOCK)); memcpy (out_row[2][1][0], in_row[2][1][x * 2 + 0], sizeof(JBLOCK)); memcpy (out_row[2][1][1], in_row[2][1][x * 2 + 1], sizeof(JBLOCK)); out_row[2][0][0][0] -= last_dc_val[2]; out_row[2][0][1][0] -= last_dc_val[2]; out_row[2][1][0][0] -= last_dc_val[2]; out_row[2][1][1][0] -= last_dc_val[2]; last_dc_val[2] = in_row[2][1][x * 2 + 1][0]; jpeg_finish_compress (&cinfo); stop = 0; for (i = 2; i < buf_size && !stop; i += size) { stop = buf[i] == 0xff && buf[i + 1] == 0xda; i += 2; size = (buf[i] << 8) | buf[i + 1]; } if (i >= buf_size) { gp_log (GP_LOG_ERROR, "ax203", "missing in ff da marker?"); return GP_ERROR_CORRUPTED_DATA; } size = buf_size - i - 2; if ((outc + size) > out_size) { gp_log (GP_LOG_ERROR, "ax203", "jpeg output buffer overflow"); return GP_ERROR_FIXED_LIMIT_EXCEEDED; } outc += copy_huffman(outbuf + outc, buf + i, size); /* Cleanup our memory dest */ free (buf); buf = NULL; buf_size = 0; } } /* We're done with the jpeg decompress (input) and compress (output) objs */ jpeg_destroy_decompress (&dinfo); jpeg_destroy_compress (&cinfo); free(regular_jpeg); return outc; } #endif