~ubuntu-branches/ubuntu/quantal/shotwell/quantal

« back to all changes in this revision

Viewing changes to src/Photo.vala

  • Committer: Package Import Robot
  • Author(s): Robert Ancell
  • Date: 2012-02-21 13:52:58 UTC
  • mto: This revision was merged to the branch mainline in revision 47.
  • Revision ID: package-import@ubuntu.com-20120221135258-ao9jiib5qicomq7q
Tags: upstream-0.11.92
ImportĀ upstreamĀ versionĀ 0.11.92

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
/* Copyright 2009-2011 Yorba Foundation
 
1
/* Copyright 2009-2012 Yorba Foundation
2
2
 *
3
3
 * This software is licensed under the GNU LGPL (version 2.1 or later).
4
4
 * See the COPYING file in this distribution. 
196
196
    // precision limitations of various subsystems.  Pixel-accuracy would be best, but barring that,
197
197
    // need to just make sure the pixbuf is in the ballpark.
198
198
    private const int SCALING_FUDGE = 64;
 
199
 
 
200
    // The number of seconds we should hold onto a precached copy of the original image; if
 
201
    // it hasn't been accessed in this many seconds, discard it to conserve memory.
 
202
    private const int PRECACHE_TIME_TO_LIVE = 180;
199
203
    
200
204
    public enum Exception {
201
205
        NONE            = 0,
292
296
    private OneShotScheduler reimport_editable_scheduler = null;
293
297
    private OneShotScheduler update_editable_attributes_scheduler = null;
294
298
    private OneShotScheduler remove_editable_scheduler = null;
 
299
 
 
300
    // The first time we have to run the pipeline on an image, we'll precache
 
301
    // a copy of the unscaled, unmodified version; this allows us to operate
 
302
    // directly on the image data quickly without re-fetching it at the top
 
303
    // of the pipeline, which can cause significant lag with larger images.
 
304
    //
 
305
    // This adds a small amount of (automatically garbage-collected) memory
 
306
    // overhead, but greatly simplifies the pipeline, since scaling can now
 
307
    // be blithely ignored, and most of the pixel operations are fast enough
 
308
    // that the app remains responsive, even with 10MP images.
 
309
    //
 
310
    // In order to make sure we discard unneeded precaches in a timely fashion,
 
311
    // we spawn a timer when the unmodified pixbuf is first precached; if the
 
312
    // timer elapses and the pixbuf hasn't been needed again since then, we'll
 
313
    // discard it and free up the memory.
 
314
    private Gdk.Pixbuf unmodified_precached = null;
 
315
    private GLib.Timer secs_since_access = null;
295
316
    
296
317
    // RAW only: developed backing photos.
297
318
    private Gee.HashMap<RawDeveloper, BackingPhotoRow?>? developments = null;
553
574
        File file = File.new_for_path(bpr.filepath);
554
575
        FileInfo info = file.query_info(DirectoryMonitor.SUPPLIED_ATTRIBUTES,
555
576
            FileQueryInfoFlags.NOFOLLOW_SYMLINKS, null);
556
 
        TimeVal timestamp;
557
 
        info.get_modification_time(out timestamp);
 
577
        TimeVal timestamp = info.get_modification_time();
558
578
        
559
579
        PhotoFileInterrogator interrogator = new PhotoFileInterrogator(
560
580
            file, PhotoFileSniffer.Options.GET_ALL);
684
704
        }
685
705
        
686
706
        notify_altered(new Alteration("image", "developer"));
 
707
        discard_prefetched(true);
687
708
    }
688
709
    
689
710
    public RawDeveloper get_raw_developer() {
992
1013
            return ImportResult.UNSUPPORTED_FORMAT;
993
1014
        }
994
1015
        
995
 
        TimeVal timestamp;
996
 
        info.get_modification_time(out timestamp);
 
1016
        TimeVal timestamp = info.get_modification_time();
997
1017
        
998
1018
        // if all MD5s supplied, don't sniff for them
999
1019
        if (params.exif_md5 != null && params.thumbnail_md5 != null && params.full_md5 != null)
1150
1170
            return null;
1151
1171
        }
1152
1172
        
1153
 
        TimeVal modification_time = TimeVal();
1154
 
        info.get_modification_time(out modification_time);
 
1173
        TimeVal modification_time = info.get_modification_time();
1155
1174
        
1156
1175
        backing.filepath = file.get_path();
1157
1176
        backing.timestamp = modification_time.tv_sec;
1198
1217
    }
1199
1218
    
1200
1219
    private class ReimportRawDevelopmentStateImpl : ReimportRawDevelopmentState {
1201
 
        class DevToReimport {
 
1220
        public class DevToReimport {
1202
1221
            public BackingPhotoRow backing = new BackingPhotoRow();
1203
1222
            public PhotoMetadata? metadata;
1204
1223
            
1491
1510
    
1492
1511
    // Use this only if the master file's modification time has been changed (i.e. touched)
1493
1512
    public void set_master_timestamp(FileInfo info) {
1494
 
        TimeVal modification;
1495
 
        info.get_modification_time(out modification);
 
1513
        TimeVal modification = info.get_modification_time();
1496
1514
        
1497
1515
        try {
1498
1516
            lock (row) {
1516
1534
    
1517
1535
    // Use this only if the editable file's modification time has been changed (i.e. touched)
1518
1536
    public void update_editable_modification_time(FileInfo info) throws DatabaseError {
1519
 
        TimeVal modification;
1520
 
        info.get_modification_time(out modification);
 
1537
        TimeVal modification = info.get_modification_time();
1521
1538
        
1522
1539
        bool altered = false;
1523
1540
        lock (row) {
2075
2092
            error("Unable to read file information for %s: %s", to_string(), err.message);
2076
2093
        }
2077
2094
        
2078
 
        TimeVal timestamp = TimeVal();
2079
 
        info.get_modification_time(out timestamp);
 
2095
        TimeVal timestamp = info.get_modification_time();
2080
2096
        
2081
2097
        // interrogate file for photo information
2082
2098
        PhotoFileInterrogator interrogator = new PhotoFileInterrogator(file);
2243
2259
        file_exif_updated();
2244
2260
    }
2245
2261
    
2246
 
    // Returns cropped and rotated dimensions
2247
 
    public override Dimensions get_dimensions() {
2248
 
        Box crop;
2249
 
        if (get_crop(out crop))
2250
 
            return crop.get_dimensions();
2251
 
        
2252
 
        return get_original_dimensions();
 
2262
    /**
 
2263
     * @brief Returns the width and height of the Photo after various
 
2264
     * arbitrary stages of the pipeline have been applied in
 
2265
     * the same order they're applied in get_pixbuf_with_options.
 
2266
     * With no argument passed, it works exactly like the
 
2267
     * previous incarnation did.
 
2268
     *
 
2269
     * @param disallowed_steps Which pipeline steps should NOT
 
2270
     *      be taken into account when computing image dimensions
 
2271
     *      (matching the convention set by get_pixbuf_with_options()).
 
2272
     *      Pipeline steps that do not affect the image geometry are
 
2273
     *      ignored.
 
2274
     */
 
2275
    public override Dimensions get_dimensions(Exception disallowed_steps = Exception.NONE) {
 
2276
        // The raw dimensions of the incoming image prior to the pipeline.
 
2277
        Dimensions returned_dims = get_raw_dimensions();
 
2278
 
 
2279
        // Compute how much the image would be resized by after rotating and/or mirroring.
 
2280
        if (disallowed_steps.allows(Exception.ORIENTATION)) {
 
2281
            Orientation ori_tmp = get_orientation();
 
2282
 
 
2283
            // Is this image rotated 90 or 270 degrees?
 
2284
            switch (ori_tmp) {
 
2285
                case Orientation.LEFT_TOP:
 
2286
                case Orientation.RIGHT_TOP:
 
2287
                case Orientation.LEFT_BOTTOM:
 
2288
                case Orientation.RIGHT_BOTTOM:
 
2289
                    // Yes, swap width and height of raw dimensions.
 
2290
                    int width_tmp = returned_dims.width;
 
2291
 
 
2292
                    returned_dims.width = returned_dims.height;
 
2293
                    returned_dims.height = width_tmp;
 
2294
                break;
 
2295
 
 
2296
                default:
 
2297
                    // No, only mirrored or rotated 180; do nothing.
 
2298
                break;
 
2299
            }
 
2300
        }
 
2301
 
 
2302
        // Compute how much the image would be resized by after straightening.
 
2303
        if (disallowed_steps.allows(Exception.STRAIGHTEN)) {
 
2304
            double x_size, y_size;
 
2305
            double angle = 0.0;
 
2306
 
 
2307
            get_straighten(out angle);
 
2308
 
 
2309
            compute_arb_rotated_size(returned_dims.width, returned_dims.height, angle, out x_size, out y_size);
 
2310
 
 
2311
            returned_dims.width = (int) (x_size);
 
2312
            returned_dims.height = (int) (y_size);
 
2313
        }
 
2314
 
 
2315
        // Compute how much the image would be resized by after cropping.
 
2316
        if (disallowed_steps.allows(Exception.CROP)) {
 
2317
            Box crop;
 
2318
            if (get_crop(out crop)) {
 
2319
                returned_dims = crop.get_dimensions();
 
2320
            }
 
2321
        }
 
2322
        return returned_dims;
2253
2323
    }
2254
2324
    
2255
2325
    // This method *must* be called with row locked.
2709
2779
    }
2710
2780
 
2711
2781
    // All instances are against the coordinate system of the unrotated photo.
2712
 
    private void add_raw_redeye_instance(EditingTools.RedeyeInstance redeye) {
 
2782
    public void add_redeye_instance(EditingTools.RedeyeInstance redeye) {
2713
2783
        KeyValueMap map = get_transformation("redeye");
2714
2784
        if (map == null) {
2715
2785
            map = new KeyValueMap("redeye");
2911
2981
                out scaled_to_viewport);
2912
2982
            original_orientation = get_original_orientation();
2913
2983
        }
2914
 
        
 
2984
 
2915
2985
        // load-and-decode and scale
2916
2986
        Gdk.Pixbuf pixbuf = load_raw_pixbuf(scaling, Exception.NONE, fetch_mode);
2917
2987
            
2921
2991
#endif
2922
2992
        if (rotate)
2923
2993
            pixbuf = original_orientation.rotate_pixbuf(pixbuf);
 
2994
 
2924
2995
#if MEASURE_PIPELINE
2925
2996
        orientation_time = timer.elapsed();
2926
 
        
 
2997
 
2927
2998
        debug("MASTER PIPELINE %s (%s): orientation=%lf total=%lf", to_string(), scaling.to_string(),
2928
2999
            orientation_time, total_timer.elapsed());
2929
3000
#endif
2930
 
        
 
3001
 
2931
3002
        return pixbuf;
2932
3003
    }
2933
 
    
 
3004
 
2934
3005
    public override Gdk.Pixbuf get_pixbuf(Scaling scaling) throws Error {
2935
3006
        return get_pixbuf_with_options(scaling);
2936
3007
    }
 
3008
 
 
3009
    /**
 
3010
     * @brief Populates the cached version of the unmodified image.
 
3011
     */
 
3012
    public void populate_prefetched() throws Error {
 
3013
        lock (unmodified_precached) {
 
3014
            // If we don't have it already, precache the original...
 
3015
            if (unmodified_precached == null) {
 
3016
                unmodified_precached = load_raw_pixbuf(Scaling.for_original(), Exception.ALL, BackingFetchMode.SOURCE);
 
3017
                secs_since_access = new GLib.Timer();
 
3018
                GLib.Timeout.add_seconds(5, (GLib.SourceFunc)discard_prefetched);
 
3019
                debug("spawning new precache timeout for %s", this.to_string()); 
 
3020
            }
 
3021
        }
 
3022
    }
 
3023
 
 
3024
    /**
 
3025
     * @brief Get a copy of what's in the cache.
 
3026
     *
 
3027
     * @return A Pixbuf with the image data from unmodified_precached.
 
3028
     */
 
3029
    public Gdk.Pixbuf? get_prefetched_copy() {
 
3030
        lock (unmodified_precached) {
 
3031
            if (unmodified_precached == null) {
 
3032
                try {
 
3033
                    populate_prefetched();
 
3034
                } catch (Error e) {
 
3035
                    warning("raw pixbuf for %s could not be loaded", this.to_string());
 
3036
                    return null;
 
3037
                }
 
3038
            }
 
3039
 
 
3040
            return unmodified_precached.copy();
 
3041
        }
 
3042
    }
 
3043
 
 
3044
    /**
 
3045
     * @brief Discards the cached version of the unmodified image.
 
3046
     *
 
3047
     * @param immed Whether the cached version should be discarded now, or not.
 
3048
     */
 
3049
    public bool discard_prefetched(bool immed = false) {
 
3050
        lock (unmodified_precached) {
 
3051
            if (secs_since_access == null)
 
3052
                return false;
 
3053
            
 
3054
            double tmp;
 
3055
            if ((secs_since_access.elapsed(out tmp) > PRECACHE_TIME_TO_LIVE) || (immed)) {
 
3056
                debug("pipeline not run in over %d seconds or got immediate command, discarding" + 
 
3057
                    "cached original for %s",
 
3058
                    PRECACHE_TIME_TO_LIVE, to_string());
 
3059
                unmodified_precached = null;
 
3060
                secs_since_access = null;
 
3061
                return false;
 
3062
            }
 
3063
 
 
3064
            return true;
 
3065
        }
 
3066
    }
2937
3067
    
2938
 
    // Returns a fully transformed and scaled pixbuf.  Transformations may be excluded via the mask.
2939
 
    // If the image is smaller than the scaling, it will be returned in its actual size.  The
2940
 
    // caller is responsible for scaling thereafter.
2941
 
    //
2942
 
    // Note that an unscaled fetch can be extremely expensive, and it's far better to specify an 
2943
 
    // appropriate scale.
 
3068
    /**
 
3069
     * @brief Returns a fully transformed and scaled pixbuf.  Transformations may be excluded via
 
3070
     * the mask. If the image is smaller than the scaling, it will be returned in its actual size.
 
3071
     * The caller is responsible for scaling thereafter.
 
3072
     *
 
3073
     * @param scaling A scaling object that describes the size the output pixbuf should be.
 
3074
     * @param exceptions The parts of the pipeline that should be skipped; defaults to NONE if
 
3075
     *      left unset.
 
3076
     * @param fetch_mode The fetch mode; if left unset, defaults to BASELINE so that
 
3077
     *      we get the image exactly as it is in the file.
 
3078
     */ 
2944
3079
    public Gdk.Pixbuf get_pixbuf_with_options(Scaling scaling, Exception exceptions =
2945
3080
        Exception.NONE, BackingFetchMode fetch_mode = BackingFetchMode.BASELINE) throws Error {
 
3081
 
2946
3082
#if MEASURE_PIPELINE
2947
3083
        Timer timer = new Timer();
2948
3084
        Timer total_timer = new Timer();
2951
3087
 
2952
3088
        total_timer.start();
2953
3089
#endif
2954
 
        
 
3090
 
2955
3091
        // If this is a RAW photo, ensure the development is ready.
2956
3092
        if (Photo.develop_raw_photos_to_files &&
2957
3093
            get_master_file_format() == PhotoFileFormat.RAW && 
2959
3095
            || fetch_mode == BackingFetchMode.SOURCE) &&
2960
3096
            !is_raw_developer_complete(get_raw_developer()))
2961
3097
                set_raw_developer(get_raw_developer());
2962
 
        
 
3098
 
2963
3099
        // to minimize holding the row lock, fetch everything needed for the pipeline up-front
2964
3100
        bool is_scaled, is_cropped, is_straightened;
2965
 
        Dimensions scaled_image, scaled_to_viewport;
 
3101
        Dimensions scaled_to_viewport;
2966
3102
        Dimensions original = Dimensions();
2967
3103
        Dimensions scaled = Dimensions();
2968
3104
        EditingTools.RedeyeInstance[] redeye_instances = null;
2970
3106
        double straightening_angle;
2971
3107
        PixelTransformer transformer = null;
2972
3108
        Orientation orientation;
2973
 
        
 
3109
 
2974
3110
        lock (row) {
2975
 
            // it's possible for get_raw_pixbuf to not return an image scaled to the spec'd scaling,
2976
 
            // particularly when the raw crop is smaller than the viewport
2977
 
            is_scaled = calculate_pixbuf_dimensions(scaling, exceptions, out scaled_image,
2978
 
                out scaled_to_viewport);
2979
 
            
2980
 
            if (is_scaled)
2981
 
                original = get_raw_dimensions();
2982
 
            
 
3111
            original = get_dimensions(Exception.ALL);
 
3112
            scaled = scaling.get_scaled_dimensions(get_dimensions(exceptions));
 
3113
            scaled_to_viewport = scaled;
 
3114
            
 
3115
            is_scaled = !(get_dimensions().equals(scaled));
 
3116
                        
2983
3117
            redeye_instances = get_raw_redeye_instances();
2984
3118
            
2985
3119
            is_cropped = get_raw_crop(out crop);
2986
 
            
 
3120
 
2987
3121
            is_straightened = get_raw_straighten(out straightening_angle);
2988
3122
            
2989
3123
            if (has_color_adjustments())
2990
3124
                transformer = get_pixel_transformer();
2991
 
            
 
3125
 
2992
3126
            orientation = get_orientation();
2993
3127
        }
2994
3128
        
2995
3129
        //
2996
3130
        // Image load-and-decode
2997
3131
        //
2998
 
        
2999
 
        Gdk.Pixbuf pixbuf = load_raw_pixbuf(scaling, exceptions, fetch_mode);
3000
 
        
3001
 
        if (is_scaled)
3002
 
            scaled = Dimensions.for_pixbuf(pixbuf);
 
3132
        populate_prefetched();
 
3133
 
 
3134
        Gdk.Pixbuf pixbuf = get_prefetched_copy();
 
3135
 
 
3136
        // remember to delete the cached copy if it isn't being used.
 
3137
        secs_since_access.start();
 
3138
        debug("pipeline being run against %s, timer restarted.", this.to_string());
 
3139
 
 
3140
        assert(pixbuf != null);
3003
3141
        
3004
3142
        //
3005
3143
        // Image transformation pipeline
3007
3145
        
3008
3146
        // redeye reduction
3009
3147
        if (exceptions.allows(Exception.REDEYE)) {
 
3148
            
3010
3149
#if MEASURE_PIPELINE
3011
3150
            timer.start();
3012
3151
#endif
3013
3152
            foreach (EditingTools.RedeyeInstance instance in redeye_instances) {
3014
 
                // redeye is stored in raw coordinates; need to scale to scaled image coordinates
3015
 
                if (is_scaled) {
3016
 
                    instance.center = coord_scaled_in_space(instance.center.x, instance.center.y, 
3017
 
                        original, scaled);
3018
 
                    instance.radius = radius_scaled_in_space(instance.radius, original, scaled);
3019
 
                    assert(instance.radius != -1);
3020
 
                }
3021
 
                
3022
3153
                pixbuf = do_redeye(pixbuf, instance);
3023
3154
            }
3024
3155
#if MEASURE_PIPELINE
3031
3162
#if MEASURE_PIPELINE
3032
3163
            timer.start();
3033
3164
#endif
3034
 
            if(is_straightened) {
 
3165
            if (is_straightened) {
3035
3166
                pixbuf = rotate_arb(pixbuf, straightening_angle);
3036
3167
            }
 
3168
            
3037
3169
#if MEASURE_PIPELINE
3038
3170
            straighten_time = timer.elapsed();
3039
3171
#endif
3040
 
        }       
 
3172
        }
3041
3173
 
3042
3174
        // crop
3043
3175
        if (exceptions.allows(Exception.CROP)) {
3045
3177
            timer.start();
3046
3178
#endif
3047
3179
            if (is_cropped) {
3048
 
                // crop is stored in raw coordinates; need to scale to scaled image coordinates;
3049
 
                // also, no need to do this if the image itself was unscaled (which can happen
3050
 
                // if the crop is smaller than the viewport)
3051
 
                if (is_scaled)
3052
 
                    crop = crop.get_scaled_similar(original, scaled);
3053
 
                
 
3180
 
 
3181
                // ensure the crop region stays inside the scaled image boundaries and is
 
3182
                // at least 1 px by 1 px; this is needed as a work-around for inaccuracies
 
3183
                // which can occur when zooming.
 
3184
                crop.left = crop.left.clamp(0, pixbuf.width - 2);
 
3185
                crop.top = crop.top.clamp(0, pixbuf.height - 2);
 
3186
 
 
3187
                crop.right = crop.right.clamp(crop.left + 1, pixbuf.width - 1);
 
3188
                crop.bottom = crop.bottom.clamp(crop.top + 1, pixbuf.height - 1);
 
3189
 
3054
3190
                pixbuf = new Gdk.Pixbuf.subpixbuf(pixbuf, crop.left, crop.top, crop.get_width(),
3055
 
                    crop.get_height());
 
3191
                     crop.get_height());
3056
3192
            }
3057
3193
 
3058
3194
#if MEASURE_PIPELINE
3059
3195
            crop_time = timer.elapsed();
3060
3196
#endif
3061
3197
        }
 
3198
    
 
3199
        // orientation (all modifications are stored in unrotated coordinate system)
 
3200
        if (exceptions.allows(Exception.ORIENTATION)) {
 
3201
#if MEASURE_PIPELINE
 
3202
            timer.start();
 
3203
#endif
 
3204
            pixbuf = orientation.rotate_pixbuf(pixbuf);
 
3205
#if MEASURE_PIPELINE
 
3206
            orientation_time = timer.elapsed();
 
3207
#endif
 
3208
        }
3062
3209
        
3063
 
        // color adjustment
 
3210
#if MEASURE_PIPELINE
 
3211
        debug("PIPELINE %s (%s): redeye=%lf crop=%lf adjustment=%lf orientation=%lf total=%lf",
 
3212
            to_string(), scaling.to_string(), redeye_time, crop_time, adjustment_time, 
 
3213
            orientation_time, total_timer.elapsed());
 
3214
#endif
 
3215
 
 
3216
        // scale the scratch image, as needed.
 
3217
        if (is_scaled) {
 
3218
            pixbuf = pixbuf.scale_simple(scaled_to_viewport.width, scaled_to_viewport.height, Gdk.InterpType.BILINEAR);
 
3219
        }
 
3220
 
 
3221
        // color adjustment; we do this dead last, since, if an image has been scaled down,
 
3222
        // it may allow us to reduce the amount of pixel arithmetic, increasing responsiveness.
3064
3223
        if (exceptions.allows(Exception.ADJUST)) {
3065
3224
#if MEASURE_PIPELINE
3066
3225
            timer.start();
3070
3229
#if MEASURE_PIPELINE
3071
3230
            adjustment_time = timer.elapsed();
3072
3231
#endif
3073
 
        }
 
3232
        }        
3074
3233
 
3075
 
        // orientation (all modifications are stored in unrotated coordinate system)
3076
 
        if (exceptions.allows(Exception.ORIENTATION)) {
3077
 
#if MEASURE_PIPELINE
3078
 
            timer.start();
3079
 
#endif
3080
 
            pixbuf = orientation.rotate_pixbuf(pixbuf);
3081
 
#if MEASURE_PIPELINE
3082
 
            orientation_time = timer.elapsed();
3083
 
#endif
3084
 
        }
3085
 
        
3086
3234
        // This is to verify the generated pixbuf matches the scale requirements; crop, straighten 
3087
3235
        // and orientation are all transformations that change the dimensions or aspect ratio of 
3088
3236
        // the pixbuf, and must be accounted for the test to be valid.
3089
3237
        if ((is_scaled) && (!is_straightened))
3090
3238
            assert(scaled_to_viewport.approx_equals(Dimensions.for_pixbuf(pixbuf), SCALING_FUDGE));
3091
 
        
3092
 
#if MEASURE_PIPELINE
3093
 
        debug("PIPELINE %s (%s): redeye=%lf crop=%lf adjustment=%lf orientation=%lf total=%lf",
3094
 
            to_string(), scaling.to_string(), redeye_time, crop_time, adjustment_time, 
3095
 
            orientation_time, total_timer.elapsed());
3096
 
#endif
3097
 
        
 
3239
 
3098
3240
        return pixbuf;
3099
3241
    }
 
3242
 
3100
3243
    
3101
3244
    //
3102
3245
    // File export
3425
3568
        editable_monitor = null;
3426
3569
    }
3427
3570
    
3428
 
    private void attach_editable(PhotoFileFormat file_format, File file) throws Error {
 
3571
    private void attach_editable(PhotoFileFormat file_format, File file) throws Error { 
3429
3572
        // remove the transformations ... this must be done before attaching the editable, as these 
3430
3573
        // transformations are in the master's coordinate system, not the editable's ... don't 
3431
3574
        // notify photo is altered *yet* because update_editable will notify, and want to avoid 
3439
3582
    }
3440
3583
    
3441
3584
    public void reimport_editable() throws Error {
3442
 
        // remove transformations, for much the same reasons as attach_editable().
3443
 
        internal_remove_all_transformations(false);
3444
3585
        update_editable(false, null);
3445
3586
    }
3446
3587
    
3502
3643
                return;
3503
3644
            }
3504
3645
            
3505
 
            TimeVal timestamp;
3506
 
            info.get_modification_time(out timestamp);
 
3646
            TimeVal timestamp = info.get_modification_time();
3507
3647
        
3508
3648
            BackingPhotoTable.get_instance().update_attributes(editable_id, timestamp.tv_sec,
3509
3649
                info.get_size());
3665
3805
                // ignored
3666
3806
            break;
3667
3807
        }
 
3808
 
 
3809
        // at this point, any image date we have cached is stale,
 
3810
        // so delete it and force the pipeline to re-fetch it
 
3811
        discard_prefetched(true);
3668
3812
    }
3669
3813
    
3670
3814
    private void on_reimport_editable() {
 
3815
        // delete old image data and force the pipeline to load new from file.
 
3816
        discard_prefetched(true);
 
3817
        
3671
3818
        debug("Reimporting editable for %s", to_string());
3672
3819
        try {
3673
3820
            reimport_editable();
3728
3875
            return false;
3729
3876
        }
3730
3877
        
3731
 
        Dimensions dim = get_raw_dimensions();
 
3878
        Dimensions dim = get_dimensions(Exception.CROP | Exception.ORIENTATION);
3732
3879
        Orientation orientation = get_orientation();
3733
3880
        
3734
3881
        crop = orientation.rotate_box(dim, raw);
3737
3884
    }
3738
3885
    
3739
3886
    // Sets the crop against the coordinate system of the rotated photo
3740
 
    public void set_crop(Box crop) {
3741
 
        Dimensions dim = get_raw_dimensions();
 
3887
    public void set_crop(Box crop) {                                                                
 
3888
        Dimensions dim = get_dimensions(Exception.CROP | Exception.ORIENTATION);
3742
3889
        Orientation orientation = get_orientation();
3743
3890
 
3744
3891
        Box derotated = orientation.derotate_box(dim, crop);
3745
 
        
3746
 
        assert(derotated.get_width() <= dim.width);
3747
 
        assert(derotated.get_height() <= dim.height);
 
3892
 
 
3893
        derotated.left = derotated.left.clamp(0, dim.width - 2);
 
3894
        derotated.right = derotated.right.clamp(derotated.left, dim.width - 1);
 
3895
 
 
3896
        derotated.top = derotated.top.clamp(0, dim.height - 2);
 
3897
        derotated.bottom = derotated.bottom.clamp(derotated.top, dim.height - 1);
3748
3898
        
3749
3899
        set_raw_crop(derotated);
3750
3900
    }
3760
3910
        set_raw_straighten(theta);
3761
3911
    }
3762
3912
    
3763
 
    public void add_redeye_instance(EditingTools.RedeyeInstance inst_unscaled) {
3764
 
        Gdk.Rectangle bounds_rect_unscaled = EditingTools.RedeyeInstance.to_bounds_rect(inst_unscaled);
3765
 
        Gdk.Rectangle bounds_rect_raw = unscaled_to_raw_rect(bounds_rect_unscaled);
3766
 
        EditingTools.RedeyeInstance inst = EditingTools.RedeyeInstance.from_bounds_rect(bounds_rect_raw);
3767
 
        
3768
 
        add_raw_redeye_instance(inst);
3769
 
    }
3770
 
 
3771
3913
    private Gdk.Pixbuf do_redeye(Gdk.Pixbuf pixbuf, EditingTools.RedeyeInstance inst) {
3772
3914
        /* we remove redeye within a circular region called the "effect
3773
3915
           extent." the effect extent is inscribed within its "bounding
3909
4051
            lower_right.y = temp;
3910
4052
        }
3911
4053
        
3912
 
        Gdk.Rectangle raw_rect = {0};
 
4054
        Gdk.Rectangle raw_rect = Gdk.Rectangle();
3913
4055
        raw_rect.x = upper_left.x;
3914
4056
        raw_rect.y = upper_left.y;
3915
4057
        raw_rect.width = lower_right.x - upper_left.x;