diff --git a/rtdata/languages/Deutsch b/rtdata/languages/Deutsch
index cdfb8b047..5c452c97b 100644
--- a/rtdata/languages/Deutsch
+++ b/rtdata/languages/Deutsch
@@ -796,6 +796,11 @@ SAVEDLG_PUTTOQUEUETAIL;An das Ende der Warteschlange für Verarbeitung legen
SAVEDLG_PUTTOQUEUE;In Warteschlange für Verarbeitung legen
SAVEDLG_SAVEIMMEDIATELY;Sofort speichern
SAVEDLG_SAVESPP;Prozessparameter mit dem Bild speichern
+SAVEDLG_SUBSAMP;Subsampling
+SAVEDLG_SUBSAMP_1;Beste Kompression
+SAVEDLG_SUBSAMP_2;Ausbalanciert
+SAVEDLG_SUBSAMP_3;Beste Qualität
+SAVEDLG_SUBSAMP_TOOLTIP;Beste Kompression: 4:1:1\nAusbalanciert: 4:2:2\nBeste Qualität: 4:4:4
SAVEDLG_TIFFFILTER;TIFF-Datei
SAVEDLG_TIFFUNCOMPRESSED;Uncompressed TIFF
TOOLBAR_TOOLTIP_CROP;Ausschnitt wählen C\n\nZum Verschieben des Ausschnitts muss die Umschalttaste gedrückt gehalten werden
diff --git a/rtdata/languages/default b/rtdata/languages/default
index 909ab7fe4..ff89b70bf 100644
--- a/rtdata/languages/default
+++ b/rtdata/languages/default
@@ -807,6 +807,11 @@ SAVEDLG_PUTTOQUEUETAIL;Put to the end of the processing queue
SAVEDLG_PUTTOQUEUE;Put into processing queue
SAVEDLG_SAVEIMMEDIATELY;Save immediately
SAVEDLG_SAVESPP;Save processing parameters with image
+SAVEDLG_SUBSAMP;Subsampling
+SAVEDLG_SUBSAMP_1;Best compression
+SAVEDLG_SUBSAMP_2;Balanced
+SAVEDLG_SUBSAMP_3;Best quality
+SAVEDLG_SUBSAMP_TOOLTIP;Best Compression: 4:1:1\nBalanced: 4:2:2\nBest quality: 4:4:4
SAVEDLG_TIFFFILTER;TIFF files
SAVEDLG_TIFFUNCOMPRESSED;Uncompressed TIFF
SAVEDLG_WARNFILENAME;File will be named
diff --git a/rtengine/iimage.h b/rtengine/iimage.h
index 148dcc8e8..237dbc1a4 100644
--- a/rtengine/iimage.h
+++ b/rtengine/iimage.h
@@ -56,7 +56,7 @@ namespace rtengine {
* @param fname is the name of the file
* @param quality is the quality of the jpeg (0...100), set it to -1 to use default
@return the error code, 0 if none */
- virtual int saveAsJPEG (Glib::ustring fname, int quality = 100)=0;
+ virtual int saveAsJPEG (Glib::ustring fname, int quality = 100, int subSamp = 3 )=0;
/** Saves the image to file in a tif format.
* @param fname is the name of the file
* @param bps can be 8 or 16 depending on the bits per pixels the output file will have
diff --git a/rtengine/image16.h b/rtengine/image16.h
index f14831a2e..08cdf8d4e 100644
--- a/rtengine/image16.h
+++ b/rtengine/image16.h
@@ -80,7 +80,7 @@ class Image16 : public ImageIO, public IImage16 {
virtual int getBitsPerPixel () { return 16; }
virtual int saveToFile (Glib::ustring fname) { return save (fname); }
virtual int saveAsPNG (Glib::ustring fname, int compression = -1, int bps = -1) { return savePNG (fname, compression, bps); }
- virtual int saveAsJPEG (Glib::ustring fname, int quality = 100) { return saveJPEG (fname, quality); }
+ virtual int saveAsJPEG (Glib::ustring fname, int quality = 100, int subSamp = 3) { return saveJPEG (fname, quality, subSamp); }
virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) { return saveTIFF (fname, bps, uncompressed); }
virtual void setSaveProgressListener (ProgressListener* pl) { return setProgressListener (pl); }
virtual void free () { delete this; }
diff --git a/rtengine/image8.h b/rtengine/image8.h
index 404e602c1..42d91d905 100644
--- a/rtengine/image8.h
+++ b/rtengine/image8.h
@@ -60,7 +60,7 @@ class Image8 : public ImageIO, public IImage8 {
virtual int getBitsPerPixel () { return 16; }
virtual int saveToFile (Glib::ustring fname) { return save (fname); }
virtual int saveAsPNG (Glib::ustring fname, int compression = -1, int bps = -1) { return savePNG (fname, compression, bps); }
- virtual int saveAsJPEG (Glib::ustring fname, int quality = 100) { return saveJPEG (fname, quality); }
+ virtual int saveAsJPEG (Glib::ustring fname, int quality = 100, int subSamp = 3) { return saveJPEG (fname, quality, subSamp); }
virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) { return saveTIFF (fname, bps, uncompressed); }
virtual void setSaveProgressListener (ProgressListener* pl) { setProgressListener (pl); }
virtual void free () { delete this; }
diff --git a/rtengine/imagefloat.h b/rtengine/imagefloat.h
index 5edadb18e..b3bea4b95 100644
--- a/rtengine/imagefloat.h
+++ b/rtengine/imagefloat.h
@@ -81,7 +81,7 @@ class Imagefloat : public ImageIO, public IImagefloat {
virtual int getBitsPerPixel () { return 16; }
virtual int saveToFile (Glib::ustring fname) { return save (fname); }
virtual int saveAsPNG (Glib::ustring fname, int compression = -1, int bps = -1) { return savePNG (fname, compression, bps); }
- virtual int saveAsJPEG (Glib::ustring fname, int quality = 100) { return saveJPEG (fname, quality); }
+ virtual int saveAsJPEG (Glib::ustring fname, int quality = 100, int subSamp = 3) { return saveJPEG (fname, quality, subSamp); }
virtual int saveAsTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false) { return saveTIFF (fname, bps, uncompressed); }
virtual void setSaveProgressListener (ProgressListener* pl) { return setProgressListener (pl); }
virtual void free () { delete this; }
diff --git a/rtengine/imageio.cc b/rtengine/imageio.cc
index c9f216174..922f70a31 100644
--- a/rtengine/imageio.cc
+++ b/rtengine/imageio.cc
@@ -610,7 +610,8 @@ int ImageIO::savePNG (Glib::ustring fname, int compression, volatile int bps) {
}
-int ImageIO::saveJPEG (Glib::ustring fname, int quality) {
+// Quality 0..100, subsampling: 1=low quality, 2=medium, 3=high
+int ImageIO::saveJPEG (Glib::ustring fname, int quality, int subSamp) {
jpeg_compress_struct cinfo;
jpeg_error_mgr jerr;
@@ -650,6 +651,20 @@ int ImageIO::saveJPEG (Glib::ustring fname, int quality) {
if (quality>=0 && quality<=100)
jpeg_set_quality (&cinfo, quality, true);
+ cinfo.comp_info[1].h_samp_factor=cinfo.comp_info[1].v_samp_factor = 1;
+ cinfo.comp_info[2].h_samp_factor=cinfo.comp_info[2].v_samp_factor = 1;
+
+ if (subSamp==1) {
+ // Best compression, default of the JPEG library: 2x2, 1x1, 1x1 (4:1:1)
+ cinfo.comp_info[0].h_samp_factor=cinfo.comp_info[0].v_samp_factor = 2;
+ } else if (subSamp==2) {
+ // Widely used normal ratio 2x1, 1x1, 1x1 (4:2:2)
+ cinfo.comp_info[0].h_samp_factor = 2; cinfo.comp_info[0].v_samp_factor = 1;
+ } else if (subSamp==3) {
+ // Best quality 1x1 1x1 1x1 (4:4:4)
+ cinfo.comp_info[0].h_samp_factor=cinfo.comp_info[0].v_samp_factor = 1;
+ }
+
jpeg_start_compress(&cinfo, TRUE);
// buffer for exif and iptc markers
diff --git a/rtengine/imageio.h b/rtengine/imageio.h
index ea08bbc0f..bfa884c9b 100644
--- a/rtengine/imageio.h
+++ b/rtengine/imageio.h
@@ -76,7 +76,7 @@ class ImageIO {
int loadPPMFromMemory(const char* buffer,int width,int height, bool swap, int bps);
int savePNG (Glib::ustring fname, int compression = -1, volatile int bps = -1);
- int saveJPEG (Glib::ustring fname, int quality = 100);
+ int saveJPEG (Glib::ustring fname, int quality = 100, int subSamp=3);
int saveTIFF (Glib::ustring fname, int bps = -1, bool uncompressed = false);
cmsHPROFILE getEmbeddedProfile () { return embProfile; }
diff --git a/rtgui/batchqueue.cc b/rtgui/batchqueue.cc
index f89c4a33b..5e3be8a2b 100644
--- a/rtgui/batchqueue.cc
+++ b/rtgui/batchqueue.cc
@@ -391,7 +391,7 @@ rtengine::ProcessingJob* BatchQueue::imageReady (rtengine::IImage16* img) {
else if (saveFormat.format=="png")
err = img->saveAsPNG (fname, saveFormat.pngCompression, saveFormat.pngBits);
else if (saveFormat.format=="jpg")
- err = img->saveAsJPEG (fname, saveFormat.jpegQuality);
+ err = img->saveAsJPEG (fname, saveFormat.jpegQuality, saveFormat.jpegSubSamp);
img->free ();
if (err) throw "Unable to save output file";
diff --git a/rtgui/editorpanel.cc b/rtgui/editorpanel.cc
index 6106775ef..512d990cb 100644
--- a/rtgui/editorpanel.cc
+++ b/rtgui/editorpanel.cc
@@ -949,7 +949,7 @@ bool EditorPanel::idle_saveImage (ProgressConnector *pc, Gl
ld->startFunc (sigc::bind(sigc::mem_fun(img, &rtengine::IImage16::saveAsPNG), fname, sf.pngCompression, sf.pngBits),
sigc::bind(sigc::mem_fun(*this,&EditorPanel::idle_imageSaved), ld, img, fname, sf));
else if (sf.format=="jpg")
- ld->startFunc (sigc::bind(sigc::mem_fun(img, &rtengine::IImage16::saveAsJPEG), fname, sf.jpegQuality),
+ ld->startFunc (sigc::bind(sigc::mem_fun(img, &rtengine::IImage16::saveAsJPEG), fname, sf.jpegQuality, sf.jpegSubSamp),
sigc::bind(sigc::mem_fun(*this,&EditorPanel::idle_imageSaved), ld, img, fname, sf));
} else {
Glib::ustring msg_ = Glib::ustring("") + fname + ": Error during image processing\n";
diff --git a/rtgui/options.cc b/rtgui/options.cc
index 95c719a1d..7c6dfea1d 100644
--- a/rtgui/options.cc
+++ b/rtgui/options.cc
@@ -196,7 +196,8 @@ void Options::setDefaults () {
saveAsDialogHeight = 600;
savesParamsAtExit = true;
saveFormat.format = "jpg";
- saveFormat.jpegQuality = 100;
+ saveFormat.jpegQuality = 90;
+ saveFormat.jpegSubSamp = 2;
saveFormat.pngCompression = 6;
saveFormat.pngBits = 8;
saveFormat.tiffBits = 8;
@@ -204,7 +205,8 @@ void Options::setDefaults () {
saveFormat.saveParams = true;
saveFormatBatch.format = "jpg";
- saveFormatBatch.jpegQuality = 100;
+ saveFormatBatch.jpegQuality = 90;
+ saveFormatBatch.jpegSubSamp = 2;
saveFormatBatch.pngCompression = 6;
saveFormatBatch.pngBits = 8;
saveFormatBatch.tiffBits = 8;
@@ -507,6 +509,7 @@ if (keyFile.has_group ("External Editor")) {
if (keyFile.has_group ("Output")) {
if (keyFile.has_key ("Output", "Format")) saveFormat.format = keyFile.get_string ("Output", "Format");
if (keyFile.has_key ("Output", "JpegQuality")) saveFormat.jpegQuality = keyFile.get_integer ("Output", "JpegQuality");
+ if (keyFile.has_key ("Output", "JpegSubSamp")) saveFormat.jpegSubSamp = keyFile.get_integer ("Output", "JpegSubSamp");
if (keyFile.has_key ("Output", "PngCompression")) saveFormat.pngCompression = keyFile.get_integer ("Output", "PngCompression");
if (keyFile.has_key ("Output", "PngBps")) saveFormat.pngBits = keyFile.get_integer ("Output", "PngBps");
if (keyFile.has_key ("Output", "TiffBps")) saveFormat.tiffBits = keyFile.get_integer ("Output", "TiffBps");
@@ -516,6 +519,7 @@ if (keyFile.has_group ("Output")) {
if (keyFile.has_key ("Output", "FormatBatch")) saveFormatBatch.format = keyFile.get_string ("Output", "FormatBatch");
if (keyFile.has_key ("Output", "JpegQualityBatch")) saveFormatBatch.jpegQuality = keyFile.get_integer ("Output", "JpegQualityBatch");
+ if (keyFile.has_key ("Output", "JpegSubSampBatch")) saveFormatBatch.jpegSubSamp = keyFile.get_integer ("Output", "JpegSubSampBatch");
if (keyFile.has_key ("Output", "PngCompressionBatch")) saveFormatBatch.pngCompression = keyFile.get_integer ("Output", "PngCompressionBatch");
if (keyFile.has_key ("Output", "PngBpsBatch")) saveFormatBatch.pngBits = keyFile.get_integer ("Output", "PngBpsBatch");
if (keyFile.has_key ("Output", "TiffBpsBatch")) saveFormatBatch.tiffBits = keyFile.get_integer ("Output", "TiffBpsBatch");
@@ -793,6 +797,7 @@ int Options::saveToFile (Glib::ustring fname) {
keyFile.set_string ("Output", "Format", saveFormat.format);
keyFile.set_integer ("Output", "JpegQuality", saveFormat.jpegQuality);
+ keyFile.set_integer ("Output", "JpegSubSamp", saveFormat.jpegSubSamp);
keyFile.set_integer ("Output", "PngCompression", saveFormat.pngCompression);
keyFile.set_integer ("Output", "PngBps", saveFormat.pngBits);
keyFile.set_integer ("Output", "TiffBps", saveFormat.tiffBits);
@@ -801,6 +806,7 @@ int Options::saveToFile (Glib::ustring fname) {
keyFile.set_string ("Output", "FormatBatch", saveFormatBatch.format);
keyFile.set_integer ("Output", "JpegQualityBatch", saveFormatBatch.jpegQuality);
+ keyFile.set_integer ("Output", "JpegSubSampBatch", saveFormatBatch.jpegSubSamp);
keyFile.set_integer ("Output", "PngCompressionBatch", saveFormatBatch.pngCompression);
keyFile.set_integer ("Output", "PngBpsBatch", saveFormatBatch.pngBits);
keyFile.set_integer ("Output", "TiffBpsBatch", saveFormatBatch.tiffBits);
diff --git a/rtgui/options.h b/rtgui/options.h
index d5a0e8312..00bc5e5b5 100644
--- a/rtgui/options.h
+++ b/rtgui/options.h
@@ -41,6 +41,7 @@ class SaveFormat {
int pngBits;
int pngCompression;
int jpegQuality;
+ int jpegSubSamp; // 1=best compression, 3=best quality
int tiffBits;
bool tiffUncompressed;
bool saveParams;
diff --git a/rtgui/saveformatpanel.cc b/rtgui/saveformatpanel.cc
index 0826f3c5c..9d906e66f 100644
--- a/rtgui/saveformatpanel.cc
+++ b/rtgui/saveformatpanel.cc
@@ -25,6 +25,25 @@ SaveFormatPanel::SaveFormatPanel () : listener (NULL) {
jpegqual = new Adjuster (M("SAVEDLG_JPEGQUAL"), 0, 100, 1, 100);
jpegqual->setAdjusterListener (this);
jpegqual->show ();
+
+ jpegSubSampBox = Gtk::manage (new Gtk::HBox ());
+
+ jpegSubSampHead=Gtk::manage (new Gtk::Label (M("SAVEDLG_SUBSAMP") + Glib::ustring(":")) );
+ jpegSubSampHead->show ();
+ jpegSubSampBox->pack_start (*jpegSubSampHead, Gtk::PACK_SHRINK, 4);
+
+ jpegSubSamp = Gtk::manage (new MyComboBoxText ());
+ jpegSubSamp->append_text (M("SAVEDLG_SUBSAMP_1"));
+ jpegSubSamp->append_text (M("SAVEDLG_SUBSAMP_2"));
+ jpegSubSamp->append_text (M("SAVEDLG_SUBSAMP_3"));
+ jpegSubSamp->set_tooltip_text (M("SAVEDLG_SUBSAMP_TOOLTIP"));
+ jpegSubSamp->set_active (2);
+ jpegSubSamp->signal_changed().connect( sigc::mem_fun(*this, &SaveFormatPanel::formatChanged) );
+ jpegSubSamp->show ();
+
+ jpegSubSampBox->pack_end (*jpegSubSamp);
+ jpegSubSampBox->show ();
+
pngcompr = new Adjuster (M("SAVEDLG_PNGCOMPR"), 0, 6, 1, 6);
pngcompr->setAdjusterListener (this);
pngcompr->show ();
@@ -49,6 +68,7 @@ SaveFormatPanel::SaveFormatPanel () : listener (NULL) {
formatopts = Gtk::manage (new Gtk::VBox ());
formatopts->pack_start (*jpegqual, Gtk::PACK_SHRINK, 4);
+ formatopts->pack_start (*jpegSubSampBox, Gtk::PACK_SHRINK, 4);
pack_start (*formatopts, Gtk::PACK_SHRINK, 4);
savespp = Gtk::manage (new Gtk::CheckButton (M("SAVEDLG_SAVESPP")));
@@ -87,6 +107,8 @@ void SaveFormatPanel::init (SaveFormat &sf) {
else if (sf.format=="tif" && sf.tiffBits==8)
format->set_active (1);
+ jpegSubSamp->set_active (sf.jpegSubSamp-1);
+
pngcompr->setValue (sf.pngCompression);
jpegqual->setValue (sf.jpegQuality);
savespp->set_active (sf.saveParams);
@@ -110,6 +132,7 @@ SaveFormat SaveFormatPanel::getFormat () {
sf.tiffBits = 8;
sf.pngCompression = (int) pngcompr->getValue ();
sf.jpegQuality = (int) jpegqual->getValue ();
+ sf.jpegSubSamp = jpegSubSamp->get_active_row_number()+1;
sf.tiffUncompressed = tiffuncompressed->get_active();
sf.saveParams = savespp->get_active ();
return sf;
@@ -117,9 +140,10 @@ SaveFormat SaveFormatPanel::getFormat () {
void SaveFormatPanel::formatChanged () {
- if (oformat==0)
+ if (oformat==0) {
removeIfThere (formatopts, jpegqual);
- else if (oformat==3 || oformat==4)
+ removeIfThere (formatopts, jpegSubSampBox);
+ } else if (oformat==3 || oformat==4)
removeIfThere (formatopts, pngcompr);
else if (oformat==1 || oformat==2)
removeIfThere (formatopts, tiffuncompressed);
@@ -129,9 +153,10 @@ void SaveFormatPanel::formatChanged () {
return;
Glib::ustring fr = fstr[act];
- if (fr=="jpg")
+ if (fr=="jpg") {
formatopts->pack_start (*jpegqual, Gtk::PACK_SHRINK,4);
- else if (fr=="png")
+ formatopts->pack_start (*jpegSubSampBox, Gtk::PACK_SHRINK,4);
+ } else if (fr=="png")
formatopts->pack_start (*pngcompr, Gtk::PACK_SHRINK,4);
else if (fr=="tif")
formatopts->pack_start (*tiffuncompressed, Gtk::PACK_SHRINK,4);
diff --git a/rtgui/saveformatpanel.h b/rtgui/saveformatpanel.h
index cf6b148af..3f21dea97 100644
--- a/rtgui/saveformatpanel.h
+++ b/rtgui/saveformatpanel.h
@@ -38,7 +38,10 @@ class SaveFormatPanel : public Gtk::VBox, public AdjusterListener {
Adjuster* pngcompr;
Gtk::CheckButton* tiffuncompressed;
MyComboBoxText* format;
+ MyComboBoxText* jpegSubSamp;
Gtk::VBox* formatopts;
+ Gtk::HBox* jpegSubSampBox;
+ Gtk::Label* jpegSubSampHead;
int oformat;
FormatChangeListener* listener;
Glib::ustring fstr[5];