-
-
Save mildsunrise/5445f4b68e186b739074ed6a7611235d to your computer and use it in GitHub Desktop.
Lossless JPEG tiling program for non-optimized images
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
tilejpeg : lossless JPEG tiling program for non-optimized images | |
tilejpeg.cpp | |
Tile JPEG Program | |
Copyright (C) 2016 Tsukasa OI <[email protected]> | |
Based on the program code on <http://apostata.web.fc2.com/tilejpeg/index.html>. | |
Permission to use, copy, modify, and/or distribute this software for | |
any purpose with or without fee is hereby granted, provided that the | |
above copyright notice and this permission notice appear in all copies. | |
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES | |
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF | |
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR | |
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES | |
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN | |
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF | |
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. | |
*/ | |
#define TILEJPEG_PROGNAME "tilejpeg" | |
#define TILEJPEG_PROGVER "0.1" | |
#include <cassert> | |
#include <cerrno> | |
#include <cstdio> | |
#include <cstddef> | |
#include <cstdlib> | |
#include <cstring> | |
#include <limits> | |
#include <new> | |
#include <jpeglib.h> | |
#ifdef _WIN32 | |
#include <io.h> | |
#include <fcntl.h> | |
#else | |
#include <unistd.h> | |
#endif | |
enum TILEJPEG_MODE | |
{ | |
TILEJPEG_TO_TILE, | |
TILEJPEG_ARGERR, | |
TILEJPEG_HELP, | |
TILEJPEG_VERSION, | |
}; | |
enum TILEJPEG_STATUS | |
{ | |
TILEJPEG_OTHER, | |
TILEJPEG_READING, | |
TILEJPEG_TILING, | |
TILEJPEG_WRITING, | |
}; | |
static enum TILEJPEG_MODE prog_mode = TILEJPEG_ARGERR; | |
static enum TILEJPEG_STATUS prog_status = TILEJPEG_OTHER; | |
static const char* prog_name = TILEJPEG_PROGNAME; | |
static const char* prog_cur_filename = nullptr; | |
static bool is_trim_enabled = false; | |
static bool is_edge_trim_enabled = false; | |
static JDIMENSION trim_x = 0, trim_y = 0; // Trim to fixed size {X} by {Y} (before tiling image) | |
static JDIMENSION tile_x = 0, tile_y = 0; // Tile {X} by {Y} images | |
static const char* out_filename = nullptr; | |
// Print error message and exit (override with standard error_exit) | |
static void tilejpeg_exit_with_error(j_common_ptr cptr) | |
{ | |
char msg[JMSG_LENGTH_MAX]; | |
cptr->err->format_message(cptr, msg); | |
if (prog_cur_filename) | |
{ | |
const char* msg_format = "%s: jpeglib raised an error while processing (%s)"; | |
switch (prog_status) | |
{ | |
case TILEJPEG_READING: | |
msg_format = "%s: jpeglib raised an error while reading (%s)"; | |
break; | |
case TILEJPEG_TILING: | |
msg_format = "%s: jpeglib raised an error while making tiled image (%s)"; | |
break; | |
case TILEJPEG_WRITING: | |
msg_format = "%s: jpeglib raised an error while writing (%s)"; | |
break; | |
} | |
fprintf(stderr, msg_format, prog_cur_filename, msg); | |
} | |
else | |
{ | |
const char* msg_format = "error: jpeglib raised an error while processing (%s)"; | |
switch (prog_status) | |
{ | |
case TILEJPEG_TILING: | |
msg_format = "error: jpeglib raised an error while making tiled image (%s)"; | |
break; | |
case TILEJPEG_WRITING: | |
msg_format = "error: jpeglib raised an error while writing image (%s)"; | |
break; | |
} | |
fprintf(stderr, msg_format, msg); | |
} | |
exit(1); | |
} | |
static bool parse_arg_dimension(const char* arg, JDIMENSION* x, JDIMENSION* y) | |
{ | |
long tmp = 0; | |
char* strp = nullptr; | |
if (!*arg) | |
{ | |
fprintf(stderr, "error: on dimension `%s': cannot be empty.\n", arg); | |
return false; | |
} | |
errno = 0; | |
tmp = strtol(arg, &strp, 10); | |
if (arg == strp) | |
{ | |
fprintf(stderr, "error: on dimension `%s': cannot parse X dimension value.\n", arg); | |
return false; | |
} | |
if (tmp <= 0) | |
{ | |
fprintf(stderr, "error: on dimension `%s': X dimension value must be positive.\n", arg); | |
return false; | |
} | |
if ((errno == ERANGE && tmp == std::numeric_limits<long>::max()) || tmp > JPEG_MAX_DIMENSION) | |
{ | |
fprintf(stderr, "error: on dimension `%s': X dimension value is too large.\n", arg); | |
return false; | |
} | |
*x = static_cast<JDIMENSION>(tmp); | |
if (*strp++ != 'x') | |
{ | |
fprintf(stderr, "error: on dimension `%s': must specified by the format {X}x{Y}.\n", arg); | |
return false; | |
} | |
if (!*strp) | |
{ | |
fprintf(stderr, "error: on dimension `%s': Y dimension value must not be empty.\n", arg); | |
return false; | |
} | |
tmp = strtol(strp, &strp, 10); | |
if (*strp) | |
{ | |
fprintf(stderr, "error: on dimension `%s': cannot parse Y dimension value.\n", arg); | |
return false; | |
} | |
if (tmp <= 0) | |
{ | |
fprintf(stderr, "error: on dimension `%s': Y dimension value must be positive.\n", arg); | |
return false; | |
} | |
if ((errno == ERANGE && tmp == std::numeric_limits<long>::max()) || tmp > JPEG_MAX_DIMENSION) | |
{ | |
fprintf(stderr, "error: on dimension `%s': Y dimension value is too large.\n", arg); | |
return false; | |
} | |
*y = static_cast<JDIMENSION>(tmp); | |
return true; | |
} | |
static void parse_args(int argc, char** argv, int* files_start) | |
{ | |
prog_name = argv[0]; | |
prog_mode = TILEJPEG_ARGERR; | |
int p = 1; | |
bool is_tile_given = false; | |
for (; p < argc; p++) | |
{ | |
if (argv[p][0] == '-') | |
{ | |
size_t l = strlen(argv[p]); | |
if (!strcmp(argv[p] + 1, "t") | |
|| !strcmp(argv[p] + 1, "-tile") | |
|| !strncmp(argv[p] + 1, "-tile=", 6)) | |
{ | |
// -t DIM | --tile DIM | --tile=DIM | |
const char* a = nullptr; | |
if (l == 2 || l == 6) | |
{ | |
if (++p == argc) | |
{ | |
fprintf(stderr, "error: `%s' option requires dimension.\n", argv[p - 1]); | |
return; | |
} | |
a = argv[p]; | |
} | |
else | |
{ | |
a = argv[p] + 6 + 1; | |
} | |
if (!parse_arg_dimension(a, &tile_x, &tile_y)) | |
return; | |
is_tile_given = true; | |
} | |
else if (!strcmp(argv[p] + 1, "T") | |
|| !strcmp(argv[p] + 1, "-trim") | |
|| !strncmp(argv[p] + 1, "-trim=", 6)) | |
{ | |
// -T DIM | --trim DIM | --trim=DIM | |
const char* a = nullptr; | |
if (l == 2 || l == 6) | |
{ | |
if (++p == argc) | |
{ | |
fprintf(stderr, "error: `%s' option requires dimension.\n", argv[p - 1]); | |
return; | |
} | |
a = argv[p]; | |
} | |
else | |
{ | |
a = argv[p] + 6 + 1; | |
} | |
if (!parse_arg_dimension(a, &trim_x, &trim_y)) | |
return; | |
is_trim_enabled = true; | |
} | |
else if (!strcmp(argv[p] + 1, "o") | |
|| !strcmp(argv[p] + 1, "-output") | |
|| !strncmp(argv[p] + 1, "-output=", 8)) | |
{ | |
// -o FILENAME | --output FILENAME | --output=FILENAME | |
const char* a = nullptr; | |
if (l == 2 || l == 8) | |
{ | |
if (++p == argc) | |
{ | |
fprintf(stderr, "error: `%s' option requires file name.\n", argv[p - 1]); | |
return; | |
} | |
a = argv[p]; | |
} | |
else | |
{ | |
a = argv[p] + 8 + 1; | |
} | |
out_filename = a; | |
} | |
else if (!strcmp(argv[p] + 1, "h") || !strcmp(argv[p] + 1, "-help")) | |
{ | |
// -h | --help | |
prog_mode = TILEJPEG_HELP; | |
return; | |
} | |
else if (!strcmp(argv[p] + 1, "v") || !strcmp(argv[p] + 1, "-version")) | |
{ | |
// -v | --version | |
prog_mode = TILEJPEG_VERSION; | |
return; | |
} | |
else if (!strcmp(argv[p] + 1, "e") || !strcmp(argv[p] + 1, "-trim-edges")) | |
{ | |
// -e | --trim-edges | |
is_edge_trim_enabled = true; | |
} | |
else if (!strcmp(argv[p] + 1, "E") || !strcmp(argv[p] + 1, "-no-trim-edges")) | |
{ | |
// -E | --no-trim-edges | |
is_edge_trim_enabled = false; | |
} | |
else if (!strcmp(argv[p] + 1, "-")) | |
{ | |
// -- | |
++p; | |
break; | |
} | |
else | |
{ | |
fprintf(stderr, "error: unrecognized option `%s'.\n", argv[p]); | |
return; | |
} | |
} | |
else | |
{ | |
break; | |
} | |
} | |
*files_start = p; | |
if (!is_tile_given) | |
{ | |
fprintf(stderr, | |
"error: tile (-t|--tile) option must be specified.\n" | |
" use -h to show help.\n" | |
); | |
return; | |
} | |
prog_mode = TILEJPEG_TO_TILE; | |
} | |
int main(int argc, char** argv) | |
{ | |
jpeg_error_mgr errmgr = {}; | |
/* | |
Parse arguments | |
*/ | |
unsigned nfiles = 0; // number of files to be given | |
int files_start = 0; // argv[files_start] is expected to be the first file argument | |
prog_status = TILEJPEG_OTHER; | |
{ | |
parse_args(argc, argv, &files_start); | |
if (prog_mode == TILEJPEG_TO_TILE) | |
{ | |
// check extra consistencies | |
assert(tile_x != 0); | |
assert(tile_y != 0); | |
if (tile_x > std::numeric_limits<int>::max() | |
|| tile_y > std::numeric_limits<int>::max() | |
|| std::numeric_limits<int>::max() / tile_x < tile_y) | |
{ | |
// error if tile_x*tile_y overflows | |
fprintf(stderr, "error: there's too many files to be given from command line arguments.\n"); | |
prog_mode = TILEJPEG_ARGERR; | |
} | |
else if ((argc - files_start) != static_cast<int>(tile_x * tile_y)) | |
{ | |
// error if too few or too many files are given | |
fprintf(stderr, "error: number of files did not match.\n"); | |
prog_mode = TILEJPEG_ARGERR; | |
} | |
} | |
switch (prog_mode) | |
{ | |
case TILEJPEG_TO_TILE: | |
break; | |
case TILEJPEG_HELP: | |
fprintf( | |
stderr, | |
"usage: %s\n [--output FILE] --tile {X}x{Y} [--trim {X}x{Y}] FILES...\n" | |
" -o|--output FILE write the output to FILE (defaults to stdout)\n" | |
" -t|--tile {X}x{Y} tile X files horizontally and Y files vertically\n" | |
" -T|--trim {X}x{Y} trim images to {X}x{Y} size first\n" | |
" -e|--trim-edges enable trimming edges for rightmost or bottom tiles\n" | |
" -E|--no-trim-edges disable trimming edges for rightmost or\n" | |
" bottom tiles (default)\n", | |
prog_name | |
); | |
return 0; | |
case TILEJPEG_VERSION: | |
fprintf( | |
stderr, | |
TILEJPEG_PROGNAME " " TILEJPEG_PROGVER " by Tsukasa OI.\n" | |
" Based on `TileJpeg' program code on <http://apostata.web.fc2.com/tilejpeg/index.html>.\n" | |
); | |
return 0; | |
case TILEJPEG_ARGERR: // fall-through | |
default: | |
return 1; | |
} | |
nfiles = static_cast<unsigned>(tile_x * tile_y); | |
} | |
// Please note that this program performs tiling only once (per process). | |
// So minor memory leaks are ignored because they are completely harmless. | |
/* | |
Read JPEG files to tile | |
*/ | |
jpeg_decompress_struct* in_images = new(std::nothrow) jpeg_decompress_struct[nfiles]; | |
jvirt_barray_ptr** in_coeffs = new(std::nothrow) jvirt_barray_ptr*[nfiles]; | |
if (!in_images || !in_coeffs) | |
{ | |
perror(prog_name); | |
return 1; | |
} | |
prog_status = TILEJPEG_READING; | |
{ | |
bool quant_tbls_used[NUM_QUANT_TBLS] = {}; | |
jpeg_decompress_struct* in_images_s = in_images; | |
jvirt_barray_ptr** in_coeffs_s = in_coeffs; | |
char** files = argv + files_start; | |
for (JDIMENSION y = 0; y < tile_y; y++) | |
{ | |
for (JDIMENSION x = 0; x < tile_x; x++, in_images_s++, in_coeffs_s++) | |
{ | |
// read JPEG file | |
jpeg_decompress_struct* in_image = in_images_s; | |
prog_cur_filename = *files++; | |
FILE* fp = fopen(prog_cur_filename, "rb"); | |
if (!fp) | |
{ | |
perror(prog_cur_filename); | |
return 1; | |
} | |
in_image->err = jpeg_std_error(&errmgr); | |
in_image->err->error_exit = tilejpeg_exit_with_error; | |
jpeg_create_decompress(in_image); | |
jpeg_stdio_src(in_image, fp); | |
jpeg_read_header(in_image, TRUE); | |
*in_coeffs_s = jpeg_read_coefficients(in_image); | |
jpeg_stdio_src(in_image, nullptr); | |
fclose(fp); | |
// check image | |
if (in_image->arith_code) | |
{ | |
fprintf(stderr, "%s: arithmetic coded JPEG file is not supported.\n", prog_cur_filename); | |
return 1; | |
} | |
if (in_image->scale_num != in_image->scale_denom) | |
{ | |
fprintf(stderr, "%s: JPEG file with DCT scaling is not supported.\n", prog_cur_filename); | |
return 1; | |
} | |
if (in_image->scale_num != in_image->scale_denom) | |
{ | |
fprintf(stderr, "%s: JPEG file with DCT scaling is not supported.\n", prog_cur_filename); | |
return 1; | |
} | |
if (in_image->jpeg_color_space == JCS_UNKNOWN) | |
{ | |
fprintf(stderr, "%s: unknown JPEG colorspace.\n", prog_cur_filename); | |
return 1; | |
} | |
// check that all images have the same color definitions and quantization tables | |
if (in_image == in_images) | |
{ | |
// first image | |
for (int i = 0; i < in_image->num_components; i++) | |
quant_tbls_used[in_image->comp_info[i].quant_tbl_no] = true; | |
if (is_trim_enabled) | |
{ | |
if (trim_x % static_cast<JDIMENSION>(in_image->max_h_samp_factor * 8) != 0) | |
{ | |
fprintf(stderr, "%s: trim width is not a multiple of MCU width.\n", prog_cur_filename); | |
return 1; | |
} | |
if (trim_y % static_cast<JDIMENSION>(in_image->max_v_samp_factor * 8) != 0) | |
{ | |
fprintf(stderr, "%s: trim height is not a multiple of MCU width.\n", prog_cur_filename); | |
return 1; | |
} | |
} | |
} | |
else | |
{ | |
// second or later images | |
jpeg_decompress_struct* image_0 = in_images + 0; | |
if (in_image->num_components != image_0->num_components) | |
{ | |
fprintf(stderr, "%s: image didn't have the same number of color components.\n", prog_cur_filename); | |
return 1; | |
} | |
if (in_image->jpeg_color_space != image_0->jpeg_color_space) | |
{ | |
fprintf(stderr, "%s: image didn't have the same color space.\n", prog_cur_filename); | |
return 1; | |
} | |
for (int i = 0; i < image_0->num_components; i++) | |
{ | |
// Note: although it could rearrange color components, it doesn't. | |
if (in_image->comp_info[i].component_id != image_0->comp_info[i].component_id | |
|| in_image->comp_info[i].h_samp_factor != image_0->comp_info[i].h_samp_factor | |
|| in_image->comp_info[i].v_samp_factor != image_0->comp_info[i].v_samp_factor | |
|| in_image->comp_info[i].quant_tbl_no != image_0->comp_info[i].quant_tbl_no) | |
{ | |
fprintf(stderr, "%s: image didn't have the same color component definition at index %d.\n", | |
prog_cur_filename, i); | |
return 1; | |
} | |
} | |
for (int i = 0; i < NUM_QUANT_TBLS; i++) | |
{ | |
// Note: although it could rearrange quantization tables, it doesn't. | |
if (!quant_tbls_used[i]) | |
continue; | |
if (memcmp(in_image->quant_tbl_ptrs[i]->quantval, image_0->quant_tbl_ptrs[i]->quantval, | |
sizeof(in_image->quant_tbl_ptrs[i]->quantval))) | |
{ | |
fprintf(stderr, "%s: image didn't have the same quantization table at index %d.\n", | |
prog_cur_filename, i); | |
return 1; | |
} | |
} | |
} | |
if (is_trim_enabled) | |
{ | |
// check trimmed images | |
if ((x != tile_x - 1 || is_edge_trim_enabled) && trim_x > in_image->image_width) | |
{ | |
fprintf(stderr, "%s: image width is too small to trim.\n", prog_cur_filename); | |
return 1; | |
} | |
if ((y != tile_y - 1 || is_edge_trim_enabled) && trim_y > in_image->image_height) | |
{ | |
fprintf(stderr, "%s: image height is too small to trim.\n", prog_cur_filename); | |
return 1; | |
} | |
} | |
else | |
{ | |
// check non-trimmed non-edge images | |
if (x != tile_x - 1 && in_image->image_width % static_cast<JDIMENSION>(in_image->max_h_samp_factor * 8) != 0) | |
{ | |
fprintf(stderr, "%s: image width is not a multiple of MCU width.\n", prog_cur_filename); | |
return 1; | |
} | |
if (y != tile_y - 1 && in_image->image_height % static_cast<JDIMENSION>(in_image->max_v_samp_factor * 8) != 0) | |
{ | |
fprintf(stderr, "%s: image height is not a multiple of MCU width.\n", prog_cur_filename); | |
return 1; | |
} | |
} | |
// check non-leftmost images | |
if (x != 0) | |
{ | |
jpeg_decompress_struct* image_l = in_image - 1; | |
if ((!is_trim_enabled || (!is_edge_trim_enabled && y == tile_y - 1)) | |
&& in_image->image_height != image_l->image_height) | |
{ | |
fprintf(stderr, "%s: image in the same row didn't have the same height.\n", prog_cur_filename); | |
return 1; | |
} | |
} | |
// check non-topmost images | |
if (y != 0) | |
{ | |
jpeg_decompress_struct* image_t = in_image - tile_x; | |
if ((!is_trim_enabled || (!is_edge_trim_enabled && x == tile_x - 1)) | |
&& in_image->image_width != image_t->image_width) | |
{ | |
fprintf(stderr, "%s: image in the same column didn't have the same width.\n", prog_cur_filename); | |
return 1; | |
} | |
} | |
} | |
} | |
} | |
/* | |
Tile JPEG images | |
*/ | |
prog_cur_filename = out_filename; | |
prog_status = TILEJPEG_TILING; | |
jvirt_barray_ptr* out_coeffs = new(std::nothrow) jvirt_barray_ptr[in_images[0].num_components]; | |
if (!out_coeffs) | |
{ | |
perror(prog_name); | |
return 1; | |
} | |
jpeg_compress_struct out_image; | |
out_image.err = jpeg_std_error(&errmgr); | |
out_image.err->error_exit = tilejpeg_exit_with_error; | |
jpeg_create_compress(&out_image); | |
{ | |
// compute output image size | |
JDIMENSION out_x = 0, out_y = 0; | |
for (unsigned x = 0; x < tile_x; x++) | |
{ | |
JDIMENSION w = (!is_trim_enabled || (!is_edge_trim_enabled && x == tile_x - 1)) | |
? in_images[x].image_width : trim_x; | |
if (JPEG_MAX_DIMENSION - out_x < w) | |
{ | |
fprintf(stderr, "error: output image width is too large.\n"); | |
return 1; | |
} | |
out_x += w; | |
} | |
for (unsigned y = 0; y < tile_y; y++) | |
{ | |
JDIMENSION h = (!is_trim_enabled || (!is_edge_trim_enabled && y == tile_y - 1)) | |
? in_images[y * tile_x].image_height : trim_y; | |
if (JPEG_MAX_DIMENSION - out_y < h) | |
{ | |
fprintf(stderr, "error: output image height is too large.\n"); | |
return 1; | |
} | |
out_y += h; | |
} | |
// create output JPEG image (with output parameters) | |
jpeg_copy_critical_parameters(in_images + 0, &out_image); | |
out_image.image_width = out_x; | |
out_image.image_height = out_y; | |
/* | |
Change here to 0 if you see compile errors | |
*/ | |
#if 1 | |
out_image.jpeg_width = out_x; | |
out_image.jpeg_height = out_y; | |
#endif | |
for (int i = 0; i < out_image.num_components; i++) | |
{ | |
// compute memory-based size | |
JDIMENSION mem_x = 0, mem_y = 0; | |
for (unsigned x = 0; x < tile_x; x++) | |
{ | |
mem_x += (!is_trim_enabled || (!is_edge_trim_enabled && x == tile_x - 1)) | |
? in_images[x].comp_info[i].width_in_blocks | |
: trim_x / DCTSIZE / in_images[0].max_h_samp_factor | |
* in_images[0].comp_info[i].h_samp_factor; | |
} | |
for (unsigned y = 0; y < tile_y; y++) | |
{ | |
mem_y += (!is_trim_enabled || (!is_edge_trim_enabled && y == tile_y - 1)) | |
? in_images[y * tile_x].comp_info[i].height_in_blocks | |
: trim_y / DCTSIZE / in_images[0].max_v_samp_factor | |
* in_images[0].comp_info[i].v_samp_factor; | |
} | |
while (mem_x % in_images[0].max_h_samp_factor) | |
mem_x++; | |
while (mem_y % in_images[0].max_v_samp_factor) | |
mem_y++; | |
// allocate and then realize virtual buffer | |
out_coeffs[i] = out_image.mem->request_virt_barray( | |
(j_common_ptr)&out_image, | |
JPOOL_IMAGE, TRUE, mem_x, mem_y, | |
in_images[0].comp_info[i].v_samp_factor | |
); | |
out_image.mem->realize_virt_arrays((j_common_ptr)&out_image); | |
// copy images | |
JDIMENSION yd = 0; | |
for (unsigned y = 0; y < tile_y; y++) | |
{ | |
JDIMENSION blk_y = (!is_trim_enabled || (!is_edge_trim_enabled && y == tile_y - 1)) | |
? in_images[y * tile_x].comp_info[i].height_in_blocks | |
: trim_y / DCTSIZE / in_images[0].max_v_samp_factor | |
* in_images[0].comp_info[i].v_samp_factor; | |
for (JDIMENSION yy = 0; yy < blk_y; yy++) | |
{ | |
JBLOCKROW dstrow = out_image.mem->access_virt_barray((j_common_ptr)&out_image, out_coeffs[i], yd++, 1, TRUE)[0]; | |
for (unsigned x = 0; x < tile_x; x++) | |
{ | |
JBLOCKROW srcrow = in_images[x + y * tile_x].mem->access_virt_barray( | |
(j_common_ptr)(in_images + x + y * tile_x), | |
in_coeffs[x + y * tile_x][i], | |
yy, 1, FALSE | |
)[0]; | |
JDIMENSION blk_x = (!is_trim_enabled || (!is_edge_trim_enabled && x == tile_x - 1)) | |
? in_images[x].comp_info[i].width_in_blocks | |
: trim_x / DCTSIZE / in_images[0].max_h_samp_factor | |
* in_images[0].comp_info[i].h_samp_factor; | |
memcpy(dstrow, srcrow, blk_x * sizeof(srcrow[0])); | |
dstrow += blk_x; | |
} | |
} | |
} | |
} | |
} | |
/* | |
Write output JPEG image | |
*/ | |
prog_cur_filename = out_filename; | |
prog_status = TILEJPEG_WRITING; | |
FILE* fp = nullptr; | |
if (out_filename) | |
fp = fopen(out_filename, "wb"); | |
else | |
{ | |
#ifdef _WIN32 | |
fp = stdout; | |
if (_setmode(_fileno(stdout), _O_BINARY) == -1) | |
{ | |
perror("stdout"); | |
return 1; | |
} | |
#else | |
int fd = dup(fileno(stdout)); | |
if (fd == -1) | |
{ | |
perror("stdout"); | |
return 1; | |
} | |
fp = fdopen(fd, "wb"); | |
#endif | |
} | |
if (!fp) | |
{ | |
if (out_filename) | |
perror(out_filename); | |
else | |
perror(prog_name); | |
return 1; | |
} | |
jpeg_stdio_dest(&out_image, fp); | |
jpeg_write_coefficients(&out_image, out_coeffs); | |
jpeg_finish_compress(&out_image); | |
fclose(fp); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment