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