Skip to content

Commit

Permalink
Make subsampling method configurable.
Browse files Browse the repository at this point in the history
For now, this just allows either the default (4:2:0) or disabling subsampling
(4:4:4). Future values could allow more control over the method. This should
address #12.
  • Loading branch information
danielgtaylor committed Apr 8, 2015
1 parent d78518b commit c37a2be
Show file tree
Hide file tree
Showing 4 changed files with 48 additions and 3 deletions.
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ SmallFry | `-m smallfry` | Linear-weighted BBCQ-like ([original project](https:/

**Note**: The SmallFry algorithm may be [patented](http://www.jpegmini.com/main/technology) so use with caution.

#### Subsampling
The JPEG format allows for subsampling of the color channels to save space. For each 2x2 block of pixels per color channel (four pixels total) it can store four pixels (all of them), two pixels or a single pixel. By default, the JPEG encoder subsamples the non-luma channels to two pixels (often referred to as 4:2:0 subsampling). Most digital cameras do the same because of limitations in the human eye. This may lead to unintended behavior for specific use cases (see #12 for an example), so you can use `--subsample disable` to disable this subsampling.

#### Example Commands

```bash
# Default settings
jpeg-recompress image.jpg compressed.jpg
Expand All @@ -91,6 +96,9 @@ jpeg-recompress --accurate --quality high --min 60 image.jpg compressed.jpg
# Use SmallFry instead of SSIM
jpeg-recompress --method smallfry image.jpg compressed.jpg

# Use 4:4:4 sampling (disables subsampling).
jpeg-recmopress --subsample disable image.jpg compressed.jpg

# Remove fisheye distortion (Tokina 10-17mm on APS-C @ 10mm)
jpeg-recompress --defish 2.6 --zoom 1.2 image.jpg defished.jpg

Expand Down
16 changes: 15 additions & 1 deletion jpeg-recompress.c
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ int copyFiles = 1;
// Whether to favor accuracy over speed
int accurate = 0;

// Chroma subsampling method
int subsample = SUBSAMPLE_DEFAULT;

static void setAttempts(command_t *self) {
attempts = atoi(self->arg);
}
Expand Down Expand Up @@ -206,6 +209,16 @@ static void setTargetFromPreset() {
}
}

static void setSubsampling(command_t *self) {
if (!strcmp("default", self->arg)) {
subsample = SUBSAMPLE_DEFAULT;
} else if (!strcmp("disable", self->arg)) {
subsample = SUBSAMPLE_444;
} else {
fprintf(stderr, "Unknown sampling method '%s', using default!\n", self->arg);
}
}

// Open a file for writing
FILE *openOutput(char *name) {
if (strcmp("-", name) == 0) {
Expand Down Expand Up @@ -249,6 +262,7 @@ int main (int argc, char **argv) {
command_option(&cmd, "-r", "--ppm", "Parse input as PPM instead of JPEG", setPpm);
command_option(&cmd, "-c", "--no-copy", "Disable copying files that will not be compressed", setCopyFiles);
command_option(&cmd, "-p", "--no-progressive", "Disable progressive encoding", setNoProgressive);
command_option(&cmd, "-S", "--subsample [arg]", "Set subsampling method. Valid values: 'default', 'disable'. [default]", setSubsampling);
command_parse(&cmd, argc, argv);

if (cmd.argc < 2) {
Expand Down Expand Up @@ -329,7 +343,7 @@ int main (int argc, char **argv) {
int optimize = accurate ? 1 : (attempt ? 0 : 1);

// Recompress to a new quality level, without optimizations (for speed)
compressedSize = encodeJpeg(&compressed, original, width, height, JCS_RGB, quality, progressive, optimize);
compressedSize = encodeJpeg(&compressed, original, width, height, JCS_RGB, quality, progressive, optimize, subsample);

// Load compressed luma for quality comparison
compressedGraySize = decodeJpeg(compressed, compressedSize, &compressedGray, &width, &height, JCS_GRAYSCALE);
Expand Down
11 changes: 10 additions & 1 deletion src/util.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ unsigned long decodeJpeg(unsigned char *buf, unsigned long bufSize, unsigned cha
return row_stride * (*height);
}

unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize) {
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize, int subsample) {
long unsigned int jpegSize = 0;
struct jpeg_compress_struct cinfo;
struct jpeg_error_mgr jerr;
Expand Down Expand Up @@ -162,6 +162,15 @@ unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, in
jpeg_simple_progression(&cinfo);
}

if (subsample == SUBSAMPLE_444) {
cinfo.comp_info[0].h_samp_factor = 1;
cinfo.comp_info[0].v_samp_factor = 1;
cinfo.comp_info[1].h_samp_factor = 1;
cinfo.comp_info[1].v_samp_factor = 1;
cinfo.comp_info[2].h_samp_factor = 1;
cinfo.comp_info[2].v_samp_factor = 1;
}

jpeg_set_quality(&cinfo, quality, TRUE);

// Start the compression
Expand Down
16 changes: 15 additions & 1 deletion src/util.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,20 @@

const char *VERSION;

// Subsampling method, which defines how much of the data from
// each color channel is included in the image per 2x2 block.
// A value of 4 means all four pixels are included, while 2
// means that only two of the four are included (hence the term
// subsampling). Subsampling works really well for photos, but
// can have issues with crisp colored borders (e.g. red text).
enum SUBSAMPLING_METHOD {
// Default is 4:2:0
SUBSAMPLE_DEFAULT,
// Using 4:4:4 is more detailed and will prevent fine text
// from getting blurry (e.g. screenshots)
SUBSAMPLE_444
};

/*
Read a file into a buffer and return the length.
*/
Expand All @@ -33,7 +47,7 @@ unsigned long decodePpm(unsigned char *buf, unsigned long bufSize, unsigned char
/*
Encode a buffer of image pixels into a JPEG.
*/
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize);
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize, int subsample);

/*
Get JPEG metadata (EXIF, IPTC, XMP, etc) and return a buffer
Expand Down

0 comments on commit c37a2be

Please sign in to comment.