Skip to content

Commit c37a2be

Browse files
committed
Make subsampling method configurable.
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.
1 parent d78518b commit c37a2be

File tree

4 files changed

+48
-3
lines changed

4 files changed

+48
-3
lines changed

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,11 @@ SmallFry | `-m smallfry` | Linear-weighted BBCQ-like ([original project](https:/
7878

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

81+
#### Subsampling
82+
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.
83+
84+
#### Example Commands
85+
8186
```bash
8287
# Default settings
8388
jpeg-recompress image.jpg compressed.jpg
@@ -91,6 +96,9 @@ jpeg-recompress --accurate --quality high --min 60 image.jpg compressed.jpg
9196
# Use SmallFry instead of SSIM
9297
jpeg-recompress --method smallfry image.jpg compressed.jpg
9398

99+
# Use 4:4:4 sampling (disables subsampling).
100+
jpeg-recmopress --subsample disable image.jpg compressed.jpg
101+
94102
# Remove fisheye distortion (Tokina 10-17mm on APS-C @ 10mm)
95103
jpeg-recompress --defish 2.6 --zoom 1.2 image.jpg defished.jpg
96104

jpeg-recompress.c

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ int copyFiles = 1;
6565
// Whether to favor accuracy over speed
6666
int accurate = 0;
6767

68+
// Chroma subsampling method
69+
int subsample = SUBSAMPLE_DEFAULT;
70+
6871
static void setAttempts(command_t *self) {
6972
attempts = atoi(self->arg);
7073
}
@@ -206,6 +209,16 @@ static void setTargetFromPreset() {
206209
}
207210
}
208211

212+
static void setSubsampling(command_t *self) {
213+
if (!strcmp("default", self->arg)) {
214+
subsample = SUBSAMPLE_DEFAULT;
215+
} else if (!strcmp("disable", self->arg)) {
216+
subsample = SUBSAMPLE_444;
217+
} else {
218+
fprintf(stderr, "Unknown sampling method '%s', using default!\n", self->arg);
219+
}
220+
}
221+
209222
// Open a file for writing
210223
FILE *openOutput(char *name) {
211224
if (strcmp("-", name) == 0) {
@@ -249,6 +262,7 @@ int main (int argc, char **argv) {
249262
command_option(&cmd, "-r", "--ppm", "Parse input as PPM instead of JPEG", setPpm);
250263
command_option(&cmd, "-c", "--no-copy", "Disable copying files that will not be compressed", setCopyFiles);
251264
command_option(&cmd, "-p", "--no-progressive", "Disable progressive encoding", setNoProgressive);
265+
command_option(&cmd, "-S", "--subsample [arg]", "Set subsampling method. Valid values: 'default', 'disable'. [default]", setSubsampling);
252266
command_parse(&cmd, argc, argv);
253267

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

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

334348
// Load compressed luma for quality comparison
335349
compressedGraySize = decodeJpeg(compressed, compressedSize, &compressedGray, &width, &height, JCS_GRAYSCALE);

src/util.c

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@ unsigned long decodeJpeg(unsigned char *buf, unsigned long bufSize, unsigned cha
104104
return row_stride * (*height);
105105
}
106106

107-
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize) {
107+
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize, int subsample) {
108108
long unsigned int jpegSize = 0;
109109
struct jpeg_compress_struct cinfo;
110110
struct jpeg_error_mgr jerr;
@@ -162,6 +162,15 @@ unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, in
162162
jpeg_simple_progression(&cinfo);
163163
}
164164

165+
if (subsample == SUBSAMPLE_444) {
166+
cinfo.comp_info[0].h_samp_factor = 1;
167+
cinfo.comp_info[0].v_samp_factor = 1;
168+
cinfo.comp_info[1].h_samp_factor = 1;
169+
cinfo.comp_info[1].v_samp_factor = 1;
170+
cinfo.comp_info[2].h_samp_factor = 1;
171+
cinfo.comp_info[2].v_samp_factor = 1;
172+
}
173+
165174
jpeg_set_quality(&cinfo, quality, TRUE);
166175

167176
// Start the compression

src/util.h

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,20 @@
1010

1111
const char *VERSION;
1212

13+
// Subsampling method, which defines how much of the data from
14+
// each color channel is included in the image per 2x2 block.
15+
// A value of 4 means all four pixels are included, while 2
16+
// means that only two of the four are included (hence the term
17+
// subsampling). Subsampling works really well for photos, but
18+
// can have issues with crisp colored borders (e.g. red text).
19+
enum SUBSAMPLING_METHOD {
20+
// Default is 4:2:0
21+
SUBSAMPLE_DEFAULT,
22+
// Using 4:4:4 is more detailed and will prevent fine text
23+
// from getting blurry (e.g. screenshots)
24+
SUBSAMPLE_444
25+
};
26+
1327
/*
1428
Read a file into a buffer and return the length.
1529
*/
@@ -33,7 +47,7 @@ unsigned long decodePpm(unsigned char *buf, unsigned long bufSize, unsigned char
3347
/*
3448
Encode a buffer of image pixels into a JPEG.
3549
*/
36-
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize);
50+
unsigned long encodeJpeg(unsigned char **jpeg, unsigned char *buf, int width, int height, int pixelFormat, int quality, int progressive, int optimize, int subsample);
3751

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

0 commit comments

Comments
 (0)