Add support for loading raw images (breaks pixbuf cache)
[gmpc.git] / src / Widgets / gmpc-image-async.vala
blobf251e1efcb449b99735d695ca8005e9daa332c45
1 /* Gnome Music Player Client (GMPC)
2 * Copyright (C) 2004-2011 Qball Cow <qball@gmpclient.org>
3 * Project homepage: http://gmpclient.org/
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20 using GLib;
21 using Gtk;
22 using Gdk;
24 const string LOG_DOMAIN = "ImageAsync";
25 namespace Gmpc
27 /**
28 * Operations you can do on the image.
29 * The modified pixbuf will be stored in cache.
31 public enum ModificationType {
32 NONE = 0, // Add nothing
33 CASING = 1, // Add border and or casing
34 DARKEN = 2, // Darken the image (for backdrop)
35 DECOLOR = 4, // Remove color from image.
36 BORDER = 8
38 public class PixbufLoaderAsync : GLib.Object
40 private weak GLib.Cancellable? pcancel = null;
41 public string uri = null;
42 public Gdk.Pixbuf pixbuf {set;get;default=null;}
43 private Gtk.TreeRowReference rref = null;
44 private int width=0;
45 private int height=0;
47 public signal void pixbuf_update(Gdk.Pixbuf? pixbuf);
49 public void set_rref(Gtk.TreeRowReference rreference)
51 this.rref = rreference;
54 private void call_row_changed()
56 if(rref != null) {
57 var model = rref.get_model();
58 var path = rref.get_path();
59 Gtk.TreeIter iter;
60 if(model.get_iter(out iter, path))
62 model.row_changed(path, iter);
67 construct {
68 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Create the image loading\n" );
71 ~PixbufLoaderAsync() {
72 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Free the image loading");
73 if(this.pcancel != null) pcancel.cancel();
76 private Gdk.Pixbuf? modify_pixbuf(owned Gdk.Pixbuf? pix, int size,ModificationType casing)
78 if(pix == null) return null;
79 if((casing&ModificationType.CASING) == ModificationType.CASING)
81 if(config.get_int_with_default("metaimage", "addcase", 1) == 1)
83 int width = pix.width;
84 int height = pix.height;
85 double spineRatio = 5.0/65.0;
87 var ii = Gtk.IconTheme.get_default().lookup_icon("stylized-cover", size, 0);
88 if(ii != null) {
89 var path = ii.get_filename();
90 try {
91 var case_image = new Gdk.Pixbuf.from_file_at_scale(path, size, size, true);
93 var tempw = (int)(case_image.width*(1.0-spineRatio));
94 Gdk.Pixbuf pix2;
95 if((case_image.height/(double)height)*width < tempw) {
96 pix2 = pix.scale_simple(tempw, (int)((height*tempw)/width), Gdk.InterpType.BILINEAR);
97 }else{
98 pix2 = pix.scale_simple((int)(width*(case_image.height/(double)height)), case_image.height, Gdk.InterpType.BILINEAR);
100 var blank = new Gdk.Pixbuf(Gdk.Colorspace.RGB, true, 8, case_image.width, case_image.height);
101 blank.fill(0x000000FF);
102 tempw = (tempw >= pix2.width)? pix2.width:tempw;
103 var temph = (case_image.height > pix2.height)?pix2.height:case_image.height;
104 pix2.copy_area(0,0, tempw-1, temph-2, blank, case_image.width-tempw, 1);
105 case_image.composite(blank, 0,0,case_image.width, case_image.height, 0,0,1,1,Gdk.InterpType.BILINEAR, 250);
106 pix = (owned)blank;
107 }catch (Error e) {
108 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_WARNING,
109 "Failed to get the stylized-cover image");
113 }else{
114 Gmpc.Fix.add_border(pix);
117 if ((casing&ModificationType.DARKEN) == ModificationType.DARKEN)
119 Gmpc.Misc.darken_pixbuf(pix, 2);
121 if ((casing&ModificationType.DECOLOR) == ModificationType.DECOLOR)
123 Gmpc.Misc.decolor_pixbuf(pix, pix);
125 if ((casing&ModificationType.BORDER) == ModificationType.BORDER)
127 Gmpc.Misc.border_pixbuf(pix);
130 return pix;
134 public void set_from_raw(uchar[] data, int req_width, int req_height, ModificationType border)
136 width = req_width;
137 height = req_height;
138 /* If running cancel the current action. */
139 this.cancel();
141 GLib.Cancellable cancel= new GLib.Cancellable();
142 this.pcancel = cancel;
143 /* var pb = Gmpc.PixbufCache.lookup_icon(int.max(width,height), uri);
144 if(pb != null)
146 this.pixbuf = pb;
147 pixbuf_update(pixbuf);
148 call_row_changed();
149 return;
152 Gdk.PixbufLoader loader = new Gdk.PixbufLoader();
153 loader.size_prepared.connect(size_prepare);
154 loader.area_prepared.connect((source) => {
155 var apix = loader.get_pixbuf();
156 var afinal = this.modify_pixbuf((owned)apix, int.max(height, width),border);
158 pixbuf = afinal;
159 pixbuf_update(pixbuf);
160 call_row_changed();
162 try{
163 Gmpc.Fix.write_loader(loader, (string)data, data.length);
164 }catch ( Error e) {
165 warning("Error trying to fetch image: %s::%s", e.message,uri);
167 try {
168 loader.close();
169 }catch (Error err) {
170 debug("Error trying to parse image: %s::%s? query cancelled?", err.message,uri);
171 pixbuf_update(null);
172 call_row_changed();
173 loader = null;
174 this.pcancel = null;
175 /* Failed to load the image */
176 return;
179 if(cancel.is_cancelled())
181 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Cancelled loading of image");
182 pixbuf_update(null);
183 cancel.reset();
184 loader = null;
185 this.pcancel = null;
186 return;
189 Gdk.Pixbuf pix = loader.get_pixbuf();
190 /* Maybe another thread allready fetched it in the mean time, we want to use that... */
192 var final = Gmpc.PixbufCache.lookup_icon(int.max(height, width), uri);
193 if(final == null)
195 Gmpc.PixbufCache.add_icon(int.max(height, width),uri, final);
198 var final = this.modify_pixbuf((owned)pix, int.max(height, width),border);
199 this.pixbuf = final;
200 pixbuf_update(pixbuf);
201 call_row_changed();
202 this.pcancel = null;
203 loader = null;
205 public void set_from_file(string uri, int req_width, int req_height, ModificationType border)
207 width = req_width;
208 height = req_height;
209 /* If running cancel the current action. */
210 this.cancel();
212 this.pcancel = null;
213 this.uri = uri;
215 var pb = Gmpc.PixbufCache.lookup_icon(int.max(width,height), uri);
216 if(pb != null)
218 this.pixbuf = pb;
219 pixbuf_update(pixbuf);
220 call_row_changed();
221 return;
223 GLib.Cancellable cancel= new GLib.Cancellable();
224 this.pcancel = cancel;
225 this.load_from_file_async(uri, width,height , cancel, border);
227 public void cancel()
229 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Cancel the image loading");
230 if(this.pcancel != null) {
231 this.pcancel.cancel();
234 private void size_prepare(Gdk.PixbufLoader loader,int gwidth, int gheight)
236 double dsize = (double)(int.max(width,height));
237 int nwidth = 0, nheight = 0;
238 if(height < 0) {
239 double scale = width/(double)gwidth;
240 nwidth = width;
241 nheight = (int)(gheight*scale);
242 } else if (width < 0) {
243 double scale = height/(double)gheight;
244 nheight = height;
245 nwidth = (int)(gwidth*scale);
246 }else{
247 nwidth = (gheight >gwidth)? (int)((dsize/gheight)*gwidth): (int)dsize;
248 nheight= (gwidth > gheight )? (int)((dsize/gwidth)*gheight): (int)dsize;
250 loader.set_size(nwidth, nheight);
252 private async void load_from_file_async(string uri, int req_width, int req_height, GLib.Cancellable cancel, ModificationType border)
254 width = req_width;
255 height = req_height;
256 GLib.File file = GLib.File.new_for_path(uri);
257 size_t result = 0;
258 Gdk.PixbufLoader loader = new Gdk.PixbufLoader();
259 loader.size_prepared.connect(size_prepare);
261 loader.area_prepared.connect((source) => {
262 var apix = loader.get_pixbuf();
263 var afinal = this.modify_pixbuf((owned)apix, int.max(height, width),border);
265 pixbuf = afinal;
266 pixbuf_update(pixbuf);
267 call_row_changed();
268 });*/
269 try{
270 var stream = yield file.read_async(0, cancel);
271 if(!cancel.is_cancelled() && stream != null )
274 try {
275 uchar data[1024];
276 result = yield stream.read_async(data,0, cancel);
277 Gmpc.Fix.write_loader(loader,(string)data, result);
278 }catch ( Error erro) {
279 warning("Error trying to fetch image: %s::%s", erro.message,uri);
280 cancel.cancel();
282 }while(!cancel.is_cancelled() && result > 0);
284 }catch ( Error e) {
285 warning("Error trying to fetch image: %s::%s", e.message,uri);
287 try {
288 loader.close();
289 }catch (Error err) {
290 debug("Error trying to parse image: %s::%s? query cancelled?", err.message,uri);
291 pixbuf_update(null);
292 call_row_changed();
293 loader = null;
294 this.pcancel = null;
295 /* Failed to load the image */
296 return;
299 if(cancel.is_cancelled())
301 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Cancelled loading of image");
302 pixbuf_update(null);
303 cancel.reset();
304 loader = null;
305 this.pcancel = null;
306 return;
309 Gdk.Pixbuf pix = loader.get_pixbuf();
310 /* Maybe another thread allready fetched it in the mean time, we want to use that... */
311 var final = Gmpc.PixbufCache.lookup_icon(int.max(height, width), uri);
312 if(final == null)
314 final = this.modify_pixbuf((owned)pix, int.max(height, width),border);
315 Gmpc.PixbufCache.add_icon(int.max(height, width),uri, final);
317 this.pixbuf = final;
318 pixbuf_update(pixbuf);
319 call_row_changed();
320 this.pcancel = null;
321 loader = null;
325 public class MetaImageAsync : Gtk.Image
327 private Gmpc.PixbufLoaderAsync? loader = null;
328 public string uri = null;
330 construct {
333 ~MetaImageAsync() {
334 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Freeing metaimageasync\n");
336 public new void set_from_raw(uchar[] data, int size, ModificationType border)
338 if(loader == null) {
339 loader = new PixbufLoaderAsync();
340 loader.pixbuf_update.connect((source, pixbuf)=>{
341 this.set_from_pixbuf(pixbuf);
344 loader.set_from_raw(data, size,size, border);
346 Gdk.PixbufLoader loader = new Gdk.PixbufLoader();
347 Gmpc.Fix.write_loader(loader,(string)data, data.length);
348 loader.close();
349 Gdk.Pixbuf pix = loader.get_pixbuf();
350 this.set_from_pixbuf(pix);
355 public new void set_from_file(string uri, int size, ModificationType border)
357 this.uri = uri;
358 if(loader == null) {
359 loader = new PixbufLoaderAsync();
360 loader.pixbuf_update.connect((source, pixbuf)=>{
361 this.set_from_pixbuf(pixbuf);
364 loader.set_from_file(uri, size,size, border);
366 public new void set_from_file_at_size(string uri, int width,int height, ModificationType border)
368 this.uri = uri;
369 if(loader == null) {
370 loader = new PixbufLoaderAsync();
371 loader.pixbuf_update.connect((source, pixbuf)=>{
372 this.set_from_pixbuf(pixbuf);
375 loader.set_from_file(uri, width,height, border);
377 public void clear_now()
379 this.loader = null;
380 this.uri = null;
381 this.clear();
384 public void set_pixbuf(Gdk.Pixbuf? pb)
386 this.loader = null;
387 this.uri = null;
388 if(pb != null) {
389 this.set_from_pixbuf(pb);
390 }else{
391 this.clear();