diff --git a/src/bin/epeg_main.c b/src/bin/epeg_main.c index 9ccfb45..61fc2c2 100644 --- a/src/bin/epeg_main.c +++ b/src/bin/epeg_main.c @@ -9,19 +9,25 @@ static int verbose_flag = 0; static int thumb_width = 0; // < 0 means % of input static int thumb_height = 0; // < 0 means % of input +static int crop_top = 0; // Pixels to crop from the top +static int crop_bottom = 0; // Pixels to crop from the bottom +static int crop_left = 0; // Pixels to crop from the left +static int crop_right = 0; // Pixels to crop from the right static int max_dimension = 0; // > 0 means we reduce max(w,h) to max_dimension, with aspect preserved -static int inset_flag = 0; // Ensure specified dimensions will be covered +static int outbound_flag = 0; // Ensure specified dimensions will be covered +static int crop_flag = 0; // Crop thumbnail after scaling static int thumb_quality = 85; // Quality value from 1 to 100 static char *thumb_comment = NULL; static struct option long_options[] = { - {"verbose", no_argument, 0, 'v'}, - {"width", required_argument, 0, 'w'}, - {"height", required_argument, 0, 'h'}, - {"max", required_argument, 0, 'm'}, - {"inset", no_argument, 0, 'i'}, - {"quality", required_argument, 0, 'q'}, - {"comment", required_argument, 0, 'c'}, + {"verbose", no_argument, 0, 'v'}, + {"width", required_argument, 0, 'w'}, + {"height", required_argument, 0, 'h'}, + {"max", required_argument, 0, 'm'}, + {"outbound", no_argument, 0, 'o'}, + {"crop", no_argument, 0, 'r'}, + {"quality", required_argument, 0, 'q'}, + {"comment", required_argument, 0, 'c'}, {0, 0, 0, 0} }; @@ -33,7 +39,8 @@ usage(const char *myname) " -w, --width=[%%] set thumbnail width [%% of input]\n" " -h, --height=[%%] set thumbnail heigth [%% of input]\n" " -m, --max= reduce max(w,h) to maximum, with aspect preserved\n" - " -i, --inset cover at least the specified size (no upscaling or cropping)\n" + " -o, --outbound cover at least the specified size (no upscaling or cropping)\n" + " -r, --crop crop the resulting thumbnail (to be used with --outbound)\n" " -c, --comment= put a comment in thumbnail\n" " -q, --quality= set thumbnail quality (1-100)\n", myname); exit(0); @@ -48,7 +55,7 @@ main(int argc, char **argv) char *input_file = NULL, *output_file = NULL; char *p; - while ((c = getopt_long(argc, argv, "w:h:vic:m:q:", long_options, &option_index)) != -1) { + while ((c = getopt_long(argc, argv, "w:h:vorc:m:q:", long_options, &option_index)) != -1) { switch (c) { case 0: usage(argv[0]); @@ -90,8 +97,11 @@ main(int argc, char **argv) } if (verbose_flag) printf("thumb_quality = %d\n", thumb_quality); break; - case 'i': - inset_flag = 1; + case 'o': + outbound_flag = 1; + break; + case 'r': + crop_flag = 1; break; case 'c': thumb_comment = strdup(optarg); @@ -130,6 +140,7 @@ main(int argc, char **argv) const char *com; Epeg_Thumbnail_Info info; int w, h; + int scaled_w, scaled_h; com = epeg_comment_get(im); if (verbose_flag) if (com) printf("Comment: %s\n", com); @@ -154,24 +165,46 @@ main(int argc, char **argv) } if (max_dimension > 0) { - if (w > h ^ inset_flag) { + if (w > h ^ outbound_flag) { thumb_width = max_dimension; thumb_height = max_dimension * h / w; } else { thumb_height = max_dimension; thumb_width = max_dimension * w / h; } - } else if (inset_flag) { - thumb_width = MAX(thumb_width, thumb_height * w / h); - thumb_height = MAX(thumb_height, thumb_width * h / w); + if (outbound_flag && crop_flag) { + crop_top = (thumb_height - max_dimension) / 2; + crop_bottom = (thumb_height - max_dimension - crop_top); + crop_left = (thumb_width - max_dimension) / 2; + crop_right = (thumb_width - max_dimension - crop_left); + } + } else if (outbound_flag) { + scaled_w = thumb_height * w / h; + scaled_h = thumb_width * h / w; + if(scaled_w > thumb_width) { + if(crop_flag) { + crop_left = (scaled_w - thumb_width) / 2; + crop_right = scaled_w - thumb_width - crop_left; + } + thumb_width = scaled_w; + } + if(scaled_h > thumb_height) { + if(crop_flag) { + crop_top = (scaled_h - thumb_height) / 2; + crop_bottom = scaled_h - thumb_height - crop_top; + } + thumb_height = scaled_h; + } } } if (verbose_flag) printf("Thumb size: %dx%d\n", thumb_width, thumb_height); + if (verbose_flag) printf("Crop (TxBxLxR): %dx%dx%dx%d\n", crop_top, crop_bottom, crop_left, crop_right); epeg_decode_size_set(im, thumb_width, thumb_height); epeg_quality_set (im, thumb_quality); epeg_thumbnail_comments_enable (im, 1); epeg_comment_set (im, thumb_comment); + epeg_crop_set (im, crop_top, crop_bottom, crop_left, crop_right); epeg_file_output_set (im, output_file); epeg_encode (im); epeg_close (im); diff --git a/src/lib/Epeg.h b/src/lib/Epeg.h index 66e0347..df82661 100644 --- a/src/lib/Epeg.h +++ b/src/lib/Epeg.h @@ -62,6 +62,7 @@ extern "C" { EAPI void epeg_thumbnail_comments_get (Epeg_Image *im, Epeg_Thumbnail_Info *info); EAPI void epeg_comment_set (Epeg_Image *im, const char *comment); EAPI void epeg_quality_set (Epeg_Image *im, int quality); + EAPI void epeg_crop_set (Epeg_Image *im, int top, int bottom, int left, int right); EAPI void epeg_thumbnail_comments_enable (Epeg_Image *im, int onoff); EAPI void epeg_file_output_set (Epeg_Image *im, const char *file); EAPI void epeg_memory_output_set (Epeg_Image *im, unsigned char **data, int *size); diff --git a/src/lib/epeg_main.c b/src/lib/epeg_main.c index 03b81f6..578a7f7 100644 --- a/src/lib/epeg_main.c +++ b/src/lib/epeg_main.c @@ -647,6 +647,23 @@ epeg_quality_set(Epeg_Image *im, int quality) im->out.quality = quality; } +/** + * Crop a thumbnail after scaling + * @param im A handle to an opened Epeg image. + * @param top Pixels to remove from the top + * @param bottom Pixels to remove from the bottom + * @param left Pixels to remove from the left + * @param right Pixels to remove from the right + */ +EAPI void +epeg_crop_set(Epeg_Image *im, int top, int bottom, int left, int right) +{ + im->out.crop_t = (top > 0 ? top : 0); + im->out.crop_b = (bottom > 0 ? bottom : 0); + im->out.crop_l = (left > 0 ? left : 0); + im->out.crop_r = (right > 0 ? right : 0); +} + /** * Enable thumbnail comments in saved image. * @param im A handle to an opened Epeg image. @@ -708,8 +725,8 @@ epeg_memory_output_set(Epeg_Image *im, unsigned char **data, int *size) * * This saves the image @p im to its destination specified by * epeg_file_output_set() or epeg_memory_output_set(). The image will be - * encoded at the deoded pixel size, using the quality, comment and thumbnail - * comment settings set on the image. + * encoded at the deoded pixel size, using the quality, crop, comment and + * thumbnail comment settings set on the image. * * retval 1 - error scale * 2 - error encode @@ -762,6 +779,7 @@ epeg_close(Epeg_Image *im) if (!im) return; if (im->pixels) free(im->pixels); if (im->lines) free(im->lines); + if (im->cropped_lines) free(im->cropped_lines); if (im->in.file) free(im->in.file); if (!im->in.file) free(im->in.jinfo.src); if (im->in.f || im->in.mem.data) jpeg_destroy_decompress(&(im->in.jinfo)); @@ -983,10 +1001,22 @@ _epeg_decode(Epeg_Image *im) return 1; } + im->cropped_lines = malloc(im->in.jinfo.output_height * sizeof(char *)); + if (!im->cropped_lines) + { + free(im->pixels); + im->pixels = NULL; + free(im->lines); + im->lines = NULL; + return 1; + } + jpeg_start_decompress(&(im->in.jinfo)); - for (y = 0; y < im->in.jinfo.output_height; y++) + for (y = 0; y < im->in.jinfo.output_height; y++) { im->lines[y] = im->pixels + (y * im->in.jinfo.output_components * im->in.jinfo.output_width); + im->cropped_lines[y] = im->lines[y] + (im->out.crop_l * im->in.jinfo.output_components); + } while (im->in.jinfo.output_scanline < im->in.jinfo.output_height) { @@ -1025,7 +1055,7 @@ _epeg_scale(Epeg_Image *im) row = im->pixels + (((y * im->in.jinfo.output_height) / h) * im->in.jinfo.output_components * im->in.jinfo.output_width); dst = im->pixels + (y * im->in.jinfo.output_components * im->in.jinfo.output_width); - for (x = 0; x < im->out.w; x++) + for (x = 0; x < w; x++) { src = row + (((x * im->in.jinfo.output_width) / w) * im->in.jinfo.output_components); for (i = 0; i < im->in.jinfo.output_components; i++) @@ -1163,7 +1193,8 @@ _epeg_encode(Epeg_Image *im) struct epeg_destination_mgr *dst_mgr = NULL; int ok = 0; - if ((im->out.w < 1) || (im->out.h < 1)) return 1; + if ((im->out.w - im->out.crop_l - im->out.crop_r) < 1) return 1; + if ((im->out.h - im->out.crop_t - im->out.crop_b) < 1) return 1; if (im->out.f) return 1; if (im->out.file) @@ -1218,6 +1249,10 @@ _epeg_encode(Epeg_Image *im) } im->out.jinfo.image_width = im->out.w; im->out.jinfo.image_height = im->out.h; + if(im->cropped_lines) { + im->out.jinfo.image_width -= (im->out.crop_l + im->out.crop_r); + im->out.jinfo.image_height -= (im->out.crop_t + im->out.crop_b); + } im->out.jinfo.input_components = im->in.jinfo.output_components; im->out.jinfo.in_color_space = im->in.jinfo.out_color_space; im->out.jinfo.dct_method = im->in.jinfo.dct_method; @@ -1277,8 +1312,13 @@ _epeg_encode(Epeg_Image *im) jpeg_write_marker(&(im->out.jinfo), JPEG_APP0 + 7, buf, strlen(buf)); } - while (im->out.jinfo.next_scanline < im->out.h) - jpeg_write_scanlines(&(im->out.jinfo), &(im->lines[im->out.jinfo.next_scanline]), 1); + if(im->cropped_lines) { + while (im->out.jinfo.next_scanline < im->out.jinfo.image_height) + jpeg_write_scanlines(&(im->out.jinfo), &(im->cropped_lines[im->out.crop_t + im->out.jinfo.next_scanline]), 1); + } else { + while (im->out.jinfo.next_scanline < im->out.jinfo.image_height) + jpeg_write_scanlines(&(im->out.jinfo), &(im->lines[im->out.jinfo.next_scanline]), 1); + } jpeg_finish_compress(&(im->out.jinfo)); done: diff --git a/src/lib/epeg_private.h b/src/lib/epeg_private.h index 8de52bb..a2fccfb 100644 --- a/src/lib/epeg_private.h +++ b/src/lib/epeg_private.h @@ -30,6 +30,7 @@ struct _Epeg_Image struct stat stat_info; unsigned char *pixels; unsigned char **lines; + unsigned char **cropped_lines; char scaled : 1; @@ -64,6 +65,7 @@ struct _Epeg_Image } mem; int x, y; int w, h; + int crop_t, crop_b, crop_l, crop_r; char *comment; FILE *f; struct jpeg_compress_struct jinfo;