Commit 3fcba76b authored by Lucas Beeler's avatar Lucas Beeler

implements one-touch, toggle-able auto-enhance functionality; resolves ticket #62

parent 86d0bb11
This diff is collapsed.
......@@ -723,6 +723,19 @@ public class PhotoTable : DatabaseTable {
return true;
}
public int get_transformation_count(PhotoID photo_id) {
string trans = get_raw_transformations(photo_id);
if (trans == null)
return 0;
FixedKeyFile keyfile = new FixedKeyFile();
if (!keyfile.load_from_data(trans, trans.length, KeyFileFlags.NONE))
return 0;
string[] groups = keyfile.get_groups();
return groups.length;
}
}
public class ThumbnailCacheTable : DatabaseTable {
......
......@@ -1278,8 +1278,8 @@ public class AdjustTool : EditingTool {
public AdjustToolWindow(Gtk.Window container) {
base(container);
histogram_image_tray.set_size_request(ImageHistogram.GRAPHIC_WIDTH,
ImageHistogram.GRAPHIC_HEIGHT);
histogram_image_tray.set_size_request(RGBHistogram.GRAPHIC_WIDTH,
RGBHistogram.GRAPHIC_HEIGHT);
Gtk.Table slider_organizer = new Gtk.Table(4, 2, false);
slider_organizer.set_row_spacings(12);
......@@ -1351,19 +1351,19 @@ public class AdjustTool : EditingTool {
float exposure_param =
canvas.get_photo().get_color_adjustment(
ColorTransformationKind.EXPOSURE);
RGBTransformationKind.EXPOSURE);
adjust_tool_window.exposure_slider.set_value(exposure_param);
float saturation_param =
canvas.get_photo().get_color_adjustment(
ColorTransformationKind.SATURATION);
RGBTransformationKind.SATURATION);
adjust_tool_window.saturation_slider.set_value(saturation_param);
float tint_param =
canvas.get_photo().get_color_adjustment(
ColorTransformationKind.TINT);
RGBTransformationKind.TINT);
adjust_tool_window.tint_slider.set_value(tint_param);
float temperature_param =
canvas.get_photo().get_color_adjustment(
ColorTransformationKind.TEMPERATURE);
RGBTransformationKind.TEMPERATURE);
adjust_tool_window.temperature_slider.set_value(temperature_param);
adjust_tool_window.apply_button.clicked += on_apply;
......@@ -1380,7 +1380,7 @@ public class AdjustTool : EditingTool {
draw_to_pixbuf = canvas.get_scaled_pixbuf().copy();
adjust_tool_window.histogram_image_tray.set_from_pixbuf(
new ImageHistogram(draw_to_pixbuf).get_graphic());
new RGBHistogram(draw_to_pixbuf).get_graphic());
base.activate(canvas);
}
......@@ -1402,33 +1402,33 @@ public class AdjustTool : EditingTool {
public override void paint(Gdk.GC gc, Gdk.Drawable drawable) {
if (!suppress_effect_redraw) {
ColorTransformation tint_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.TINT,
RGBTransformation tint_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.TINT,
(float) adjust_tool_window.tint_slider.get_value());
ColorTransformation temperature_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.TEMPERATURE,
RGBTransformation temperature_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.TEMPERATURE,
(float) adjust_tool_window.temperature_slider.get_value());
ColorTransformation saturation_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.SATURATION,
RGBTransformation saturation_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.SATURATION,
(float) adjust_tool_window.saturation_slider.get_value());
ColorTransformation exposure_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.EXPOSURE,
RGBTransformation exposure_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.EXPOSURE,
(float) adjust_tool_window.exposure_slider.get_value());
ColorTransformation composite_transform = ((
RGBTransformation composite_transform = ((
exposure_transform.compose_against(
saturation_transform)).compose_against(
temperature_transform)).compose_against(
tint_transform);
ColorTransformation.transform_existing_pixbuf(composite_transform,
RGBTransformation.transform_existing_pixbuf(composite_transform,
canvas.get_scaled_pixbuf(), draw_to_pixbuf);
}
......@@ -1455,7 +1455,7 @@ public class AdjustTool : EditingTool {
suppress_effect_redraw = false;
canvas.repaint();
adjust_tool_window.histogram_image_tray.set_from_pixbuf(
new ImageHistogram(draw_to_pixbuf).get_graphic());
new RGBHistogram(draw_to_pixbuf).get_graphic());
}
private void on_apply() {
......@@ -1465,31 +1465,31 @@ public class AdjustTool : EditingTool {
AppWindow.get_instance().set_busy_cursor();
Gee.ArrayList<ColorTransformationInstance?> adjustments =
new Gee.ArrayList<ColorTransformationInstance?>();
Gee.ArrayList<RGBTransformationInstance?> adjustments =
new Gee.ArrayList<RGBTransformationInstance?>();
ColorTransformationInstance current_instance = {0};
RGBTransformationInstance current_instance = {0};
float exposure_param =
(float) adjust_tool_window.exposure_slider.get_value();
current_instance.kind = ColorTransformationKind.EXPOSURE;
current_instance.kind = RGBTransformationKind.EXPOSURE;
current_instance.parameter = exposure_param;
adjustments.add(current_instance);
float saturation_param =
(float) adjust_tool_window.saturation_slider.get_value();
current_instance.kind = ColorTransformationKind.SATURATION;
current_instance.kind = RGBTransformationKind.SATURATION;
current_instance.parameter = saturation_param;
adjustments.add(current_instance);
float tint_param =
(float) adjust_tool_window.tint_slider.get_value();
current_instance.kind = ColorTransformationKind.TINT;
current_instance.kind = RGBTransformationKind.TINT;
current_instance.parameter = tint_param;
adjustments.add(current_instance);
float temperature_param =
(float) adjust_tool_window.temperature_slider.get_value();
current_instance.kind = ColorTransformationKind.TEMPERATURE;
current_instance.kind = RGBTransformationKind.TEMPERATURE;
current_instance.parameter = temperature_param;
adjustments.add(current_instance);
......@@ -1503,7 +1503,7 @@ public class AdjustTool : EditingTool {
private void on_adjustment() {
canvas.repaint();
adjust_tool_window.histogram_image_tray.set_from_pixbuf(
new ImageHistogram(draw_to_pixbuf).get_graphic());
new RGBHistogram(draw_to_pixbuf).get_graphic());
}
private void on_canvas_resize() {
......
......@@ -24,5 +24,6 @@ public class FixedKeyFile {
// g_key_file_to_data never throws an error according to the documentation
public string to_data (out size_t length = null, out GLib.Error error = null);
public void remove_group (string group_name) throws GLib.KeyFileError;
public string[] get_groups();
}
......@@ -68,6 +68,7 @@ public abstract class TransformablePhoto: PhotoBase {
CROP = 1 << 1,
REDEYE = 1 << 2,
ADJUST = 1 << 3,
ENHANCE = 1 << 4,
ALL = 0xFFFFFFFF
}
......@@ -283,8 +284,22 @@ public abstract class TransformablePhoto: PhotoBase {
}
public bool has_transformations() {
return photo_table.has_transformations(photo_id)
|| (photo_table.get_orientation(photo_id) != photo_table.get_original_orientation(photo_id));
// trivial check -- if the photo has been reoriented, then it has transformations
if (photo_table.get_orientation(photo_id) != photo_table.get_original_orientation(photo_id))
return true;
// if execution reaches this point, we didn't return above, so perform a more
// complicated series of tests
if (!photo_table.has_transformations(photo_id))
return false;
if ((photo_table.get_transformation_count(photo_id) == 1) &&
(photo_table.get_transformation(photo_id, "enhancement") != null) &&
(!is_enhancement_enabled()))
return false;
// if we haven't returned by this point, then we have transformations other
// than disabled enhancement, so return true
return true;
}
public void remove_all_transformations() {
......@@ -354,56 +369,56 @@ public abstract class TransformablePhoto: PhotoBase {
notify_altered(Alteration.IMAGE);
}
public float get_color_adjustment(ColorTransformationKind adjust_kind) {
public float get_color_adjustment(RGBTransformationKind adjust_kind) {
KeyValueMap map = photo_table.get_transformation(photo_id, "adjustments");
if (map == null)
return 0.0f;
switch (adjust_kind) {
case ColorTransformationKind.EXPOSURE:
case RGBTransformationKind.EXPOSURE:
return (float) map.get_double("exposure", 0.0f);
case ColorTransformationKind.SATURATION:
case RGBTransformationKind.SATURATION:
return (float) map.get_double("saturation", 0.0f);
case ColorTransformationKind.TEMPERATURE:
case RGBTransformationKind.TEMPERATURE:
return (float) map.get_double("temperature", 0.0f);
case ColorTransformationKind.TINT:
case RGBTransformationKind.TINT:
return (float) map.get_double("tint", 0.0f);
default:
error("unrecognized ColorTransformationKind enumeration value");
error("unrecognized RGBTransformationKind enumeration value");
return 0.0f;
}
}
public void set_color_adjustments(Gee.ArrayList<ColorTransformationInstance?> adjustments) {
public void set_color_adjustments(Gee.ArrayList<RGBTransformationInstance?> adjustments) {
KeyValueMap map = photo_table.get_transformation(photo_id, "adjustments");
if (map == null)
map = new KeyValueMap("adjustments");
foreach (ColorTransformationInstance adjustment in adjustments) {
foreach (RGBTransformationInstance adjustment in adjustments) {
switch(adjustment.kind) {
case ColorTransformationKind.EXPOSURE:
case RGBTransformationKind.EXPOSURE:
map.set_double("exposure", adjustment.parameter);
break;
case ColorTransformationKind.SATURATION:
case RGBTransformationKind.SATURATION:
map.set_double("saturation", adjustment.parameter);
break;
case ColorTransformationKind.TEMPERATURE:
case RGBTransformationKind.TEMPERATURE:
map.set_double("temperature", adjustment.parameter);
break;
case ColorTransformationKind.TINT:
case RGBTransformationKind.TINT:
map.set_double("tint", adjustment.parameter);
break;
default:
error("unrecognized ColorTransformationKind enumeration value");
error("unrecognized RGBTransformationKind enumeration value");
break;
}
}
......@@ -467,6 +482,88 @@ public abstract class TransformablePhoto: PhotoBase {
notify_altered(Alteration.IMAGE);
}
public void set_enhancement_enabled() {
KeyValueMap map = photo_table.get_transformation(photo_id,
"enhancement");
if (map == null) {
map = new KeyValueMap("enhancement");
}
if (!map.has_key("encoded_transformation")) {
Gdk.Pixbuf histogram_pixbuf = null;
try {
histogram_pixbuf = get_pixbuf(1024, Exception.ALL, Gdk.InterpType.HYPER);
} catch (Error e) {
error("pixbuf generation failed");
}
IntensityTransformation transform =
EnhancementFactory.create_current(histogram_pixbuf);
map.set_int("version", EnhancementFactory.get_current_version());
map.set_string("encoded_transformation", transform.to_string());
}
map.set_bool("enhanced", true);
if (photo_table.set_transformation(photo_id, map))
notify_altered(Alteration.IMAGE);
}
public void set_enhancement_disabled() {
KeyValueMap map = photo_table.get_transformation(photo_id,
"enhancement");
if (map == null) {
map = new KeyValueMap("enhancement");
}
map.set_bool("enhanced", false);
if (photo_table.set_transformation(photo_id, map))
notify_altered(Alteration.IMAGE);
}
private IntensityTransformation get_enhancement_transformation() {
assert(is_enhancement_enabled());
KeyValueMap map = photo_table.get_transformation(photo_id,
"enhancement");
assert(map != null);
assert(map.has_key("encoded_transformation"));
string encoded_transformation = map.get_string("encoded_transformation",
"");
assert(encoded_transformation != "");
int version = map.get_int("version", 0);
return EnhancementFactory.create_from_encoding(version,
encoded_transformation);
}
public bool is_enhancement_enabled() {
KeyValueMap map = photo_table.get_transformation(photo_id,
"enhancement");
/* if there's no enhancement group in the map, then enhancement is
disabled */
if (map == null)
return false;
/* if there is an enhancement group in the map, but the "enhanced" key
has value false, then enhancement is disabled */
if (!map.get_bool("enhanced", true))
return false;
/* if enhanced is set to true in the map, but the encoding version used
is unsupported, then enhancement is disabled */
if (!EnhancementFactory.is_encoding_version_supported(map.get_int("version", 0)))
return false;
/* otherwise, enhancement is enabled */
return true;
}
// Returns a File that can be used for exporting ... this file should persist for a reasonable
// amount of time, as drag-and-drop exports can conclude long after the DnD source has seen
// the end of the transaction. ... However, if failure is detected, export_failed() will be
......@@ -510,8 +607,9 @@ public abstract class TransformablePhoto: PhotoBase {
#if MEASURE_PIPELINE
Timer timer = new Timer();
Timer total_timer = new Timer();
double load_and_decode_time = 0.0, pixbuf_copy_time = 0.0, redeye_time = 0.0,
adjustment_time = 0.0, crop_time = 0.0, orientation_time = 0.0, scale_time = 0.0;
double load_and_decode_time = 0.0, pixbuf_copy_time = 0.0, redeye_time = 0.0,
adjustment_time = 0.0, crop_time = 0.0, orientation_time = 0.0, scale_time = 0.0,
enhance_time = 0.0;
total_timer.start();
#endif
......@@ -600,13 +698,28 @@ public abstract class TransformablePhoto: PhotoBase {
#if MEASURE_PIPELINE
timer.start();
#endif
ColorTransformation composite_transform = get_composite_transformation();
RGBTransformation composite_transform = get_composite_transformation();
if (!composite_transform.is_identity())
ColorTransformation.transform_pixbuf(composite_transform, pixbuf);
RGBTransformation.transform_pixbuf(composite_transform, pixbuf);
#if MEASURE_PIPELINE
adjustment_time = timer.elapsed();
#endif
}
// auto-enhancement
if ((exceptions & Exception.ENHANCE) == 0) {
#if MEASURE_PIPELINE
timer.start();
#endif
if (is_enhancement_enabled()) {
IntensityTransformation adaptive_transform =
get_enhancement_transformation();
IntensityTransformation.transform_pixbuf(adaptive_transform, pixbuf);
}
#if MEASURE_PIPELINE
enhance_time = timer.elapsed();
#endif
}
// orientation (all modifications are stored in unrotated coordinate system)
if ((exceptions & Exception.ORIENTATION) == 0) {
......@@ -622,9 +735,9 @@ public abstract class TransformablePhoto: PhotoBase {
#if MEASURE_PIPELINE
double total_time = total_timer.elapsed();
debug("Pipeline: load_and_decode=%lf pixbuf_copy=%lf redeye=%lf crop=%lf scale=%lf adjustment=%lf orientation=%lf total=%lf",
load_and_decode_time, pixbuf_copy_time, redeye_time, crop_time, scale_time, adjustment_time,
orientation_time, total_time);
debug("Pipeline: load_and_decode=%lf pixbuf_copy=%lf redeye=%lf crop=%lf scale=%lf adjustment=%lf enhancement=%lf orientation=%lf total=%lf",
load_and_decode_time, pixbuf_copy_time, redeye_time, crop_time, scale_time, adjustment_time,
enhance_time, orientation_time, total_time);
#endif
return pixbuf;
......@@ -821,29 +934,29 @@ public abstract class TransformablePhoto: PhotoBase {
return pixbuf;
}
public ColorTransformation get_composite_transformation() {
float exposure_param = get_color_adjustment(ColorTransformationKind.EXPOSURE);
float saturation_param = get_color_adjustment(ColorTransformationKind.SATURATION);
float tint_param = get_color_adjustment(ColorTransformationKind.TINT);
float temperature_param = get_color_adjustment(ColorTransformationKind.TEMPERATURE);
public RGBTransformation get_composite_transformation() {
float exposure_param = get_color_adjustment(RGBTransformationKind.EXPOSURE);
float saturation_param = get_color_adjustment(RGBTransformationKind.SATURATION);
float tint_param = get_color_adjustment(RGBTransformationKind.TINT);
float temperature_param = get_color_adjustment(RGBTransformationKind.TEMPERATURE);
ColorTransformation exposure_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.EXPOSURE, exposure_param);
RGBTransformation exposure_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.EXPOSURE, exposure_param);
ColorTransformation saturation_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.SATURATION, saturation_param);
RGBTransformation saturation_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.SATURATION, saturation_param);
ColorTransformation tint_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.TINT, tint_param);
RGBTransformation tint_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.TINT, tint_param);
ColorTransformation temperature_transform =
ColorTransformationFactory.get_instance().from_parameter(
ColorTransformationKind.TEMPERATURE, temperature_param);
RGBTransformation temperature_transform =
RGBTransformationFactory.get_instance().from_parameter(
RGBTransformationKind.TEMPERATURE, temperature_param);
ColorTransformation composite_transform = ((
RGBTransformation composite_transform = ((
exposure_transform.compose_against(
saturation_transform)).compose_against(
temperature_transform)).compose_against(
......
......@@ -30,6 +30,7 @@ public abstract class EditingHostPage : SinglePhotoPage {
private Gtk.ToggleToolButton crop_button = null;
private Gtk.ToggleToolButton redeye_button = null;
private Gtk.ToggleToolButton adjust_button = null;
private Gtk.ToggleToolButton enhance_button = null;
private Gtk.ToolButton prev_button = new Gtk.ToolButton.from_stock(Gtk.STOCK_GO_BACK);
private Gtk.ToolButton next_button = new Gtk.ToolButton.from_stock(Gtk.STOCK_GO_FORWARD);
private EditingTool current_tool = null;
......@@ -70,6 +71,13 @@ public abstract class EditingHostPage : SinglePhotoPage {
adjust_button.toggled += on_adjust_toggled;
toolbar.insert(adjust_button, -1);
// ehance tool
enhance_button = new Gtk.ToggleToolButton.from_stock(Resources.ENHANCE);
enhance_button.set_label("Enhance");
enhance_button.set_tooltip_text("Automatically improve the photo's appearance");
enhance_button.toggled += on_enhance_toggled;
toolbar.insert(enhance_button, -1);
// separator to force next/prev buttons to right side of toolbar
Gtk.SeparatorToolItem separator = new Gtk.SeparatorToolItem();
separator.set_expand(true);
......@@ -140,10 +148,11 @@ public abstract class EditingHostPage : SinglePhotoPage {
photo = new_photo;
photo.altered += on_photo_altered;
set_page_name(photo.get_name());
quick_update_pixbuf();
update_ui();
}
......@@ -163,6 +172,12 @@ public abstract class EditingHostPage : SinglePhotoPage {
private void update_ui() {
bool multiple = controller.get_count() > 1;
/* disconnect the on_enhance_toggled signal before calling set_active on the
button to avoid having on_enhance_toggled invoked twice */
enhance_button.toggled -= on_enhance_toggled;
enhance_button.set_active(photo.is_enhancement_enabled());
enhance_button.toggled += on_enhance_toggled;
prev_button.sensitive = multiple;
next_button.sensitive = multiple;
}
......@@ -309,6 +324,8 @@ public abstract class EditingHostPage : SinglePhotoPage {
private void on_photo_altered(TransformablePhoto p) {
quick_update_pixbuf();
update_ui();
}
private override bool on_motion(Gdk.EventMotion event, int x, int y, Gdk.ModifierType mask) {
......@@ -514,6 +531,19 @@ public abstract class EditingHostPage : SinglePhotoPage {
adjust_button.set_active(false);
}
private void on_enhance_toggled() {
if (current_tool != null)
deactivate_tool();
AppWindow.get_instance().set_busy_cursor();
if (enhance_button.get_active()) {
photo.set_enhancement_enabled();
} else {
photo.set_enhancement_disabled();
}
AppWindow.get_instance().set_normal_cursor();
}
private void place_tool_window() {
if (current_tool == null)
return;
......
......@@ -51,6 +51,7 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
public const string MAKE_PRIMARY = "shotwell-make-primary";
public const string IMPORT = "shotwell-import";
public const string IMPORT_ALL = "shotwell-import-all";
public const string ENHANCE = "shotwell-auto-enhance";
public const string ICON_APP = "shotwell.svg";
public const string ICON_ABOUT_LOGO = "shotwell-street.jpg";
......@@ -78,6 +79,7 @@ along with Shotwell; if not, write to the Free Software Foundation, Inc.,
add_stock_icon(icons_dir.get_child("make-primary.svg"), MAKE_PRIMARY);
add_stock_icon(icons_dir.get_child("import.svg"), IMPORT);
add_stock_icon(icons_dir.get_child("import-all.png"), IMPORT_ALL);
add_stock_icon(icons_dir.get_child("enhance.png"), ENHANCE);
factory.add_default();
}
......
......@@ -73,7 +73,13 @@ public class KeyValueMap {
map.set(key, value.to_string());
}
public void set_bool(string key, bool value) {
assert(key != null);
map.set(key, value.to_string());
}
public string get_string(string key, string? def) {
string value = map.get(key);
......@@ -91,6 +97,12 @@ public class KeyValueMap {
return (value != null) ? value.to_double() : def;
}
public bool get_bool(string key, bool def) {
string value = map.get(key);
return (value != null) ? value.to_bool() : def;
}
// REDEYE: redeye reduction operates on circular regions defined by
// (Gdk.Point, int) pairs, where the Gdk.Point specifies the
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment