summaryrefslogtreecommitdiff
path: root/src/photos/GdkSupport.vala
blob: 51ae72c08529dc61ba63dd311746602cadbff8ec (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/* Copyright 2016 Software Freedom Conservancy Inc.
 *
 * This software is licensed under the GNU Lesser General Public License
 * (version 2.1 or later).  See the COPYING file in this distribution.
 */

public abstract class GdkReader : PhotoFileReader {
    public GdkReader(string filepath, PhotoFileFormat file_format) {
        base (filepath, file_format);
    }
    
    public override PhotoMetadata read_metadata() throws Error {
        PhotoMetadata metadata = new PhotoMetadata();
        metadata.read_from_file(get_file());
        
        return metadata;
    }
    
    public override Gdk.Pixbuf unscaled_read() throws Error {
        return new Gdk.Pixbuf.from_file(get_filepath());
    }
    
    public override Gdk.Pixbuf scaled_read(Dimensions full, Dimensions scaled) throws Error {
        return new Gdk.Pixbuf.from_file_at_scale(get_filepath(), scaled.width, scaled.height, false);
    }
}

public abstract class GdkSniffer : PhotoFileSniffer {
    private DetectedPhotoInformation detected = null;
    private bool size_ready = false;
    private bool area_prepared = false;
    
    public GdkSniffer(File file, PhotoFileSniffer.Options options) {
        base (file, options);
    }
    
    public override DetectedPhotoInformation? sniff(out bool is_corrupted) throws Error {
        detected = new DetectedPhotoInformation();
        
        Gdk.PixbufLoader pixbuf_loader = new Gdk.PixbufLoader();
        pixbuf_loader.size_prepared.connect(on_size_prepared);
        pixbuf_loader.area_prepared.connect(on_area_prepared);
        
        // valac chokes on the ternary operator here
        Checksum? md5_checksum = null;
        if (calc_md5)
            md5_checksum = new Checksum(ChecksumType.MD5);
        
        detected.metadata = new PhotoMetadata();
        try {
            detected.metadata.read_from_file(file);
        } catch (Error err) {
            // no metadata detected
            detected.metadata = null;
        }
        
        if (calc_md5 && detected.metadata != null) {
            uint8[]? flattened_sans_thumbnail = detected.metadata.flatten_exif(false);
            if (flattened_sans_thumbnail != null && flattened_sans_thumbnail.length > 0)
                detected.exif_md5 = md5_binary(flattened_sans_thumbnail, flattened_sans_thumbnail.length);
            
            uint8[]? flattened_thumbnail = detected.metadata.flatten_exif_preview();
            if (flattened_thumbnail != null && flattened_thumbnail.length > 0)
                detected.thumbnail_md5 = md5_binary(flattened_thumbnail, flattened_thumbnail.length);
        }
        
        // if no MD5, don't read as much, as the needed info will probably be gleaned
        // in the first 8K to 16K
        uint8[] buffer = calc_md5 ? new uint8[64 * 1024] : new uint8[8 * 1024];
        size_t count = 0;
        
        // loop through until all conditions we're searching for are met
        FileInputStream fins = file.read(null);
        for (;;) {
            size_t bytes_read = fins.read(buffer, null);
            if (bytes_read <= 0)
                break;
            
            count += bytes_read;
            
            if (calc_md5)
                md5_checksum.update(buffer, bytes_read);
            
            // keep parsing the image until the size is discovered
            if (!size_ready || !area_prepared)
                pixbuf_loader.write(buffer[0:bytes_read]);
            
            // if not searching for anything else, exit
            if (!calc_md5 && size_ready && area_prepared)
                break;
        }
        
        // PixbufLoader throws an error if you close it with an incomplete image, so trap this
        try {
            pixbuf_loader.close();
        } catch (Error err) {
        }
        
        if (fins != null)
            fins.close(null);
        
        if (calc_md5)
            detected.md5 = md5_checksum.get_string();
        
        // if size and area are not ready, treat as corrupted file (entire file was read)
        is_corrupted = !size_ready || !area_prepared;
        
        return detected;
    }
    
    private void on_size_prepared(Gdk.PixbufLoader loader, int width, int height) {
        detected.image_dim = Dimensions(width, height);
        size_ready = true;
    }
    
    private void on_area_prepared(Gdk.PixbufLoader pixbuf_loader) {
        Gdk.Pixbuf? pixbuf = pixbuf_loader.get_pixbuf();
        if (pixbuf == null)
            return;
        
        detected.colorspace = pixbuf.get_colorspace();
        detected.channels = pixbuf.get_n_channels();
        detected.bits_per_channel = pixbuf.get_bits_per_sample();
        
        unowned Gdk.PixbufFormat format = pixbuf_loader.get_format();
        detected.format_name = format.get_name();
        detected.file_format = PhotoFileFormat.from_pixbuf_name(detected.format_name);
        
        area_prepared = true;
    }
}