1/* Gnome Music Player Client (GMPC) 2 * Copyright (C) 2004-2011 Qball Cow <qball@gmpclient.org> 3 * Project homepage: http://gmpclient.org/ 4 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. 9 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. 14 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. 18*/ 19 20using GLib; 21using Gtk; 22using Gdk; 23 24const string LOG_DOMAIN = "ImageAsync"; 25namespace Gmpc 26{ 27 /** 28 * Operations you can do on the image. 29 * The modified pixbuf will be stored in cache. 30 */ 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 37 } 38 public class PixbufLoaderAsync : GLib.Object 39 { 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; 46 47 public signal void pixbuf_update(Gdk.Pixbuf? pixbuf); 48 49 public void set_rref(Gtk.TreeRowReference rreference) 50 { 51 this.rref = rreference; 52 } 53 54 private void call_row_changed() 55 { 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)) 61 { 62 model.row_changed(path, iter); 63 } 64 } 65 } 66 67 construct { 68 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Create the image loading\n" ); 69 } 70 71 ~PixbufLoaderAsync() { 72 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Free the image loading"); 73 if(this.pcancel != null) pcancel.cancel(); 74 } 75 76 private Gdk.Pixbuf? modify_pixbuf(owned Gdk.Pixbuf? pix, int size,ModificationType casing) 77 { 78 if(pix == null) return null; 79 if((casing&ModificationType.CASING) == ModificationType.CASING) 80 { 81 if(config.get_int_with_default("metaimage", "addcase", 1) == 1) 82 { 83 int width = pix.width; 84 int height = pix.height; 85 double spineRatio = 5.0/65.0; 86 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); 92 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); 99 } 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"); 110 } 111 112 } 113 }else{ 114 Gmpc.Fix.add_border(pix); 115 } 116 } 117 if ((casing&ModificationType.DARKEN) == ModificationType.DARKEN) 118 { 119 Gmpc.Misc.darken_pixbuf(pix, 2); 120 } 121 if ((casing&ModificationType.DECOLOR) == ModificationType.DECOLOR) 122 { 123 Gmpc.Misc.decolor_pixbuf(pix, pix); 124 } 125 if ((casing&ModificationType.BORDER) == ModificationType.BORDER) 126 { 127 Gmpc.Misc.border_pixbuf(pix); 128 } 129 130 return pix; 131 } 132 133 134 public new void set_from_file(string uri, int req_width, int req_height, ModificationType border) 135 { 136 width = req_width; 137 height = req_height; 138 /* If running cancel the current action. */ 139 this.cancel(); 140 141 this.pcancel = null; 142 this.uri = uri; 143 144 var pb = Gmpc.PixbufCache.lookup_icon(int.max(width,height), uri); 145 if(pb != null) 146 { 147 this.pixbuf = pb; 148 pixbuf_update(pixbuf); 149 call_row_changed(); 150 return; 151 } 152 GLib.Cancellable cancel= new GLib.Cancellable(); 153 this.pcancel = cancel; 154 this.load_from_file_async(uri, width,height , cancel, border); 155 } 156 public void cancel() 157 { 158 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Cancel the image loading"); 159 if(this.pcancel != null) { 160 this.pcancel.cancel(); 161 } 162 } 163 private void size_prepare(Gdk.PixbufLoader loader,int gwidth, int gheight) 164 { 165 double dsize = (double)(int.max(width,height)); 166 int nwidth = 0, nheight = 0; 167 if(height < 0) { 168 double scale = width/(double)gwidth; 169 nwidth = width; 170 nheight = (int)(gheight*scale); 171 } else if (width < 0) { 172 double scale = height/(double)gheight; 173 nheight = height; 174 nwidth = (int)(gwidth*scale); 175 }else{ 176 nwidth = (gheight >gwidth)? (int)((dsize/gheight)*gwidth): (int)dsize; 177 nheight= (gwidth > gheight )? (int)((dsize/gwidth)*gheight): (int)dsize; 178 } 179 loader.set_size(nwidth, nheight); 180 } 181 private async void load_from_file_async(string uri, int req_width, int req_height, GLib.Cancellable cancel, ModificationType border) 182 { 183 width = req_width; 184 height = req_height; 185 GLib.File file = GLib.File.new_for_path(uri); 186 size_t result = 0; 187 Gdk.PixbufLoader loader = new Gdk.PixbufLoader(); 188 loader.size_prepared.connect(size_prepare); 189 /* 190 loader.area_prepared.connect((source) => { 191 var apix = loader.get_pixbuf(); 192 var afinal = this.modify_pixbuf((owned)apix, int.max(height, width),border); 193 194 pixbuf = afinal; 195 pixbuf_update(pixbuf); 196 call_row_changed(); 197 });*/ 198 try{ 199 var stream = yield file.read_async(0, cancel); 200 if(!cancel.is_cancelled() && stream != null ) 201 { 202 do{ 203 try { 204 uchar data[1024]; 205 result = yield stream.read_async(data,0, cancel); 206 Gmpc.Fix.write_loader(loader,(string)data, result); 207 }catch ( Error erro) { 208 warning("Error trying to fetch image: %s::%s", erro.message,uri); 209 cancel.cancel(); 210 } 211 }while(!cancel.is_cancelled() && result > 0); 212 } 213 }catch ( Error e) { 214 warning("Error trying to fetch image: %s::%s", e.message,uri); 215 } 216 try { 217 loader.close(); 218 }catch (Error err) { 219 debug("Error trying to parse image: %s::%s? query cancelled?", err.message,uri); 220 pixbuf_update(null); 221 call_row_changed(); 222 loader = null; 223 this.pcancel = null; 224 /* Failed to load the image */ 225 return; 226 } 227 228 if(cancel.is_cancelled()) 229 { 230 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Cancelled loading of image"); 231 pixbuf_update(null); 232 cancel.reset(); 233 loader = null; 234 this.pcancel = null; 235 return; 236 } 237 238 Gdk.Pixbuf pix = loader.get_pixbuf(); 239 /* Maybe another thread allready fetched it in the mean time, we want to use that... */ 240 var final = Gmpc.PixbufCache.lookup_icon(int.max(height, width), uri); 241 if(final == null) 242 { 243 final = this.modify_pixbuf((owned)pix, int.max(height, width),border); 244 Gmpc.PixbufCache.add_icon(int.max(height, width),uri, final); 245 } 246 this.pixbuf = final; 247 pixbuf_update(pixbuf); 248 call_row_changed(); 249 this.pcancel = null; 250 loader = null; 251 } 252 } 253 254 public class MetaImageAsync : Gtk.Image 255 { 256 private Gmpc.PixbufLoaderAsync? loader = null; 257 public string uri = null; 258 259 construct { 260 } 261 262 ~MetaImageAsync() { 263 GLib.log(LOG_DOMAIN,GLib.LogLevelFlags.LEVEL_DEBUG,"Freeing metaimageasync\n"); 264 } 265 266 public new void set_from_file(string uri, int size, ModificationType border) 267 { 268 this.uri = uri; 269 if(loader == null) { 270 loader = new PixbufLoaderAsync(); 271 loader.pixbuf_update.connect((source, pixbuf)=>{ 272 this.set_from_pixbuf(pixbuf); 273 }); 274 } 275 loader.set_from_file(uri, size,size, border); 276 } 277 public new void set_from_file_at_size(string uri, int width,int height, ModificationType border) 278 { 279 this.uri = uri; 280 if(loader == null) { 281 loader = new PixbufLoaderAsync(); 282 loader.pixbuf_update.connect((source, pixbuf)=>{ 283 this.set_from_pixbuf(pixbuf); 284 }); 285 } 286 loader.set_from_file(uri, width,height, border); 287 } 288 public void clear_now() 289 { 290 this.loader = null; 291 this.uri = null; 292 this.clear(); 293 } 294 295 public void set_pixbuf(Gdk.Pixbuf? pb) 296 { 297 this.loader = null; 298 this.uri = null; 299 if(pb != null) { 300 this.set_from_pixbuf(pb); 301 }else{ 302 this.clear(); 303 } 304 } 305 } 306} 307