2
// FSpot.PixbufUtils.cs
6
// Larry Ewing <lewing@novell.com>
7
// Stephane Delcroix <stephane@declroix.org>
9
// This is free software. See COPYING for details
13
using System.Collections;
14
using System.Runtime.InteropServices;
23
public static class PixbufUtils {
24
static Pixbuf error_pixbuf = null;
25
public static Pixbuf ErrorPixbuf {
27
if (error_pixbuf == null)
28
error_pixbuf = GtkUtil.TryLoadIcon (FSpot.Global.IconTheme, "f-spot-question-mark", 256, (Gtk.IconLookupFlags)0);
32
public static Pixbuf LoadingPixbuf = PixbufUtils.LoadFromAssembly ("f-spot-loading.png");
34
public static int GetSize (Pixbuf pixbuf)
36
return Math.Max (pixbuf.Width, pixbuf.Height);
39
public static double Fit (Pixbuf pixbuf,
40
int dest_width, int dest_height,
42
out int fit_width, out int fit_height)
44
return Fit (pixbuf.Width, pixbuf.Height,
45
dest_width, dest_height,
47
out fit_width, out fit_height);
50
public static double Fit (int orig_width, int orig_height,
51
int dest_width, int dest_height,
53
out int fit_width, out int fit_height)
55
if (orig_width == 0 || orig_height == 0) {
61
double scale = Math.Min (dest_width / (double)orig_width,
62
dest_height / (double)orig_height);
64
if (scale > 1.0 && !upscale_smaller)
67
fit_width = (int) Math.Round (scale * orig_width);
68
fit_height = (int) Math.Round (scale * orig_height);
74
// FIXME: These should be in GTK#. When my patch is committed, these LoadFrom* methods will
77
public class AspectLoader {
78
Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
81
ImageOrientation orientation;
83
public AspectLoader (int max_width, int max_height)
85
this.max_height = max_height;
86
this.max_width = max_width;
87
loader.SizePrepared += HandleSizePrepared;
90
private void HandleSizePrepared (object obj, SizePreparedArgs args)
92
switch (orientation) {
93
case ImageOrientation.LeftTop:
94
case ImageOrientation.LeftBottom:
95
case ImageOrientation.RightTop:
96
case ImageOrientation.RightBottom:
98
max_width = max_height;
106
int scale_height = 0;
108
double scale = Fit (args.Width, args.Height, max_width, max_height, true, out scale_width, out scale_height);
111
loader.SetSize (scale_width, scale_height);
114
public Pixbuf Load (System.IO.Stream stream, ImageOrientation orientation)
117
byte [] data = new byte [8192];
118
while (((count = stream.Read (data, 0, data.Length)) > 0) && loader.Write (data, (ulong)count))
122
Pixbuf orig = loader.Pixbuf;
123
Gdk.Pixbuf rotated = FSpot.Utils.PixbufUtils.TransformOrientation (orig, orientation);
125
if (orig != rotated) {
132
public Pixbuf LoadFromFile (string path)
135
orientation = GetOrientation (path);
136
using (FileStream fs = System.IO.File.OpenRead (path)) {
137
return Load (fs, orientation);
139
} catch (Exception) {
140
Log.ErrorFormat ("Error loading photo {0}", path);
146
public static Pixbuf ScaleToMaxSize (Pixbuf pixbuf, int width, int height)
148
return ScaleToMaxSize (pixbuf, width, height, true);
151
public static Pixbuf ScaleToMaxSize (Pixbuf pixbuf, int width, int height, bool upscale)
154
int scale_height = 0;
155
double scale = Fit (pixbuf, width, height, upscale, out scale_width, out scale_height);
158
if (upscale || (scale < 1.0))
159
result = pixbuf.ScaleSimple (scale_width, scale_height, (scale_width > 20) ? Gdk.InterpType.Bilinear : Gdk.InterpType.Nearest);
161
result = pixbuf.Copy ();
166
static public void GetSize (string path, out int width, out int height)
168
using (Gdk.Pixbuf pixbuf = new Gdk.Pixbuf (path)) {
169
width = pixbuf.Width;
170
height = pixbuf.Height;
174
static public Pixbuf LoadAtMaxSize (string path, int max_width, int max_height)
176
PixbufUtils.AspectLoader loader = new AspectLoader (max_width, max_height);
177
return loader.LoadFromFile (path);
180
static public Pixbuf LoadFromStream (System.IO.Stream input)
182
Gdk.PixbufLoader loader = new Gdk.PixbufLoader ();
183
byte [] buffer = new byte [8192];
186
while ((n = input.Read (buffer, 0, 8192)) != 0)
187
loader.Write (buffer, (ulong) n);
191
return loader.Pixbuf;
195
public static Pixbuf TagIconFromPixbuf (Pixbuf source)
197
return IconFromPixbuf (source, (int) FSpot.Tag.IconSize.Large);
200
public static Pixbuf IconFromPixbuf (Pixbuf source, int size)
205
if (source.Width > source.Height)
206
source = tmp = new Pixbuf (source, (source.Width - source.Height) /2, 0, source.Height, source.Height);
207
else if (source.Width < source.Height)
208
source = tmp = new Pixbuf (source, 0, (source.Height - source.Width) /2, source.Width, source.Width);
210
if (source.Width == source.Height)
211
icon = source.ScaleSimple (size, size, InterpType.Bilinear);
213
throw new Exception ("Bad logic leads to bad accidents");
221
static Pixbuf LoadFromAssembly (string resource)
224
return new Pixbuf (System.Reflection.Assembly.GetEntryAssembly (), resource);
230
public static Gdk.Pixbuf ScaleToAspect (Gdk.Pixbuf orig, int width, int height)
233
double scale = Fit (orig, width, height, false, out pos.Width, out pos.Height);
234
pos.X = (width - pos.Width) / 2;
235
pos.Y = (height - pos.Height) / 2;
237
Pixbuf scaled = new Pixbuf (Colorspace.Rgb, false, 8, width, height);
238
scaled.Fill (0x000000);
240
orig.Composite (scaled, pos.X, pos.Y,
241
pos.Width, pos.Height,
242
pos.X, pos.Y, scale, scale,
243
Gdk.InterpType.Bilinear,
249
public static Pixbuf Flatten (Pixbuf pixbuf)
251
if (!pixbuf.HasAlpha)
254
Pixbuf flattened = new Pixbuf (Colorspace.Rgb, false, 8, pixbuf.Width, pixbuf.Height);
255
pixbuf.CompositeColor (flattened, 0, 0,
256
pixbuf.Width, pixbuf.Height,
259
255, 0, 0, 2000, 0xffffff, 0xffffff);
264
[DllImport ("libfspot")]
265
static extern IntPtr f_pixbuf_unsharp_mask (IntPtr src, double radius, double amount, double threshold);
267
public static Pixbuf UnsharpMask (Pixbuf src, double radius, double amount, double threshold)
269
IntPtr raw_ret = f_pixbuf_unsharp_mask (src.Handle, radius, amount, threshold);
270
Gdk.Pixbuf ret = (Gdk.Pixbuf) GLib.Object.GetObject(raw_ret, true);
274
[DllImport ("libfspot")]
275
static extern IntPtr f_pixbuf_blur (IntPtr src, double radius);
277
public static Pixbuf Blur (Pixbuf src, double radius)
279
IntPtr raw_ret = f_pixbuf_blur (src.Handle, radius);
280
Gdk.Pixbuf ret = (Gdk.Pixbuf) GLib.Object.GetObject(raw_ret, true);
284
public unsafe static Gdk.Pixbuf RemoveRedeye (Gdk.Pixbuf src, Gdk.Rectangle area)
286
return RemoveRedeye (src, area, -15);
289
public unsafe static Gdk.Pixbuf RemoveRedeye (Gdk.Pixbuf src, Gdk.Rectangle area, int threshold)
290
//threshold, factors and comparisons borrowed from the gimp plugin 'redeye.c' by Robert Merkel
292
Gdk.Pixbuf copy = src.Copy ();
293
Gdk.Pixbuf selection = new Gdk.Pixbuf (copy, area.X, area.Y, area.Width, area.Height);
294
byte *spix = (byte *)selection.Pixels;
295
int h = selection.Height;
296
int w = selection.Width;
297
int channels = src.NChannels;
299
double RED_FACTOR = 0.5133333;
300
double GREEN_FACTOR = 1;
301
double BLUE_FACTOR = 0.1933333;
303
for (int j = 0; j < h; j++) {
305
for (int i = 0; i < w; i++) {
306
int adjusted_red = (int)(s[0] * RED_FACTOR);
307
int adjusted_green = (int)(s[1] * GREEN_FACTOR);
308
int adjusted_blue = (int)(s[2] * BLUE_FACTOR);
310
if (adjusted_red >= adjusted_green - threshold
311
&& adjusted_red >= adjusted_blue - threshold)
312
s[0] = (byte)(((double)(adjusted_green + adjusted_blue)) / (2.0 * RED_FACTOR));
315
spix += selection.Rowstride;
321
public static unsafe Pixbuf ColorAdjust (Pixbuf src, double brightness, double contrast,
322
double hue, double saturation, int src_color, int dest_color)
324
Pixbuf adjusted = new Pixbuf (Colorspace.Rgb, src.HasAlpha, 8, src.Width, src.Height);
325
PixbufUtils.ColorAdjust (src, adjusted, brightness, contrast, hue, saturation, src_color, dest_color);
329
public static Cms.Format PixbufCmsFormat (Pixbuf buf)
331
return buf.HasAlpha ? Cms.Format.Rgba8Planar : Cms.Format.Rgb8;
334
public static unsafe void ColorAdjust (Pixbuf src, Pixbuf dest,
335
double brightness, double contrast,
336
double hue, double saturation,
337
int src_color, int dest_color)
339
if (src.Width != dest.Width || src.Height != dest.Height)
340
throw new Exception ("Invalid Dimensions");
342
//Cms.Profile eos10d = new Cms.Profile ("/home/lewing/ICCProfiles/EOS-10D-True-Color-Non-Linear.icm");
343
Cms.Profile srgb = Cms.Profile.CreateStandardRgb ();
345
Cms.Profile bchsw = Cms.Profile.CreateAbstract (256,
347
brightness, contrast,
348
hue, saturation, src_color,
351
Cms.Profile [] list = new Cms.Profile [] { srgb, bchsw, srgb };
352
Cms.Transform trans = new Cms.Transform (list,
353
PixbufCmsFormat (src),
354
PixbufCmsFormat (dest),
355
Cms.Intent.Perceptual, 0x0100);
357
ColorAdjust (src, dest, trans);
365
public static unsafe void ColorAdjust (Gdk.Pixbuf src, Gdk.Pixbuf dest, Cms.Transform trans)
367
int width = src.Width;
368
byte * srcpix = (byte *) src.Pixels;
369
byte * destpix = (byte *) dest.Pixels;
371
for (int row = 0; row < src.Height; row++) {
372
trans.Apply ((IntPtr) (srcpix + row * src.Rowstride),
373
(IntPtr) (destpix + row * dest.Rowstride),
379
public static unsafe bool IsGray (Gdk.Pixbuf pixbuf, int max_difference)
381
int chan = pixbuf.NChannels;
383
byte *pix = (byte *)pixbuf.Pixels;
384
int h = pixbuf.Height;
385
int w = pixbuf.Width;
386
int stride = pixbuf.Rowstride;
388
for (int j = 0; j < h; j++) {
390
for (int i = 0; i < w; i++) {
391
if (Math.Abs (p[0] - p[1]) > max_difference || Math.Abs (p[0] - p [2]) > max_difference) {
405
public static unsafe void ReplaceColor (Gdk.Pixbuf src, Gdk.Pixbuf dest)
407
if (src.HasAlpha || !dest.HasAlpha || (src.Width != dest.Width) || (src.Height != dest.Height))
408
throw new ApplicationException ("invalid pixbufs");
410
byte *dpix = (byte *)dest.Pixels;
411
byte *spix = (byte *)src.Pixels;
414
for (int j = 0; j < h; j++) {
417
for (int i = 0; i < w; i++) {
424
dpix += dest.Rowstride;
425
spix += src.Rowstride;
429
public static ImageOrientation GetOrientation (SafeUri uri)
431
using (var img = ImageFile.Create (uri)) {
432
return img.Orientation;
436
[Obsolete ("Use GetOrientation (SafeUri) instead")]
437
public static ImageOrientation GetOrientation (string path)
439
return GetOrientation (new SafeUri (path));
442
[DllImport("libgnomeui-2-0.dll")]
443
static extern IntPtr gnome_thumbnail_scale_down_pixbuf(IntPtr pixbuf, int dest_width, int dest_height);
445
public static Gdk.Pixbuf ScaleDown (Gdk.Pixbuf src, int width, int height)
447
IntPtr raw_ret = gnome_thumbnail_scale_down_pixbuf(src.Handle, width, height);
449
if (raw_ret == IntPtr.Zero)
452
ret = (Gdk.Pixbuf) GLib.Object.GetObject(raw_ret, true);
456
public static void CreateDerivedVersion (SafeUri source, SafeUri destination)
458
CreateDerivedVersion (source, destination, 95);
461
public static void CreateDerivedVersion (SafeUri source, SafeUri destination, uint jpeg_quality)
463
if (source.GetExtension () == destination.GetExtension ()) {
464
// Simple copy will do!
465
var file_from = GLib.FileFactory.NewForUri (source);
466
var file_to = GLib.FileFactory.NewForUri (destination);
467
file_from.Copy (file_to, GLib.FileCopyFlags.AllMetadata | GLib.FileCopyFlags.Overwrite, null, null);
471
// Else make a derived copy with metadata copied
472
using (var img = ImageFile.Create (source)) {
473
using (var pixbuf = img.Load ()) {
474
CreateDerivedVersion (source, destination, jpeg_quality, pixbuf);
479
public static void CreateDerivedVersion (SafeUri source, SafeUri destination, uint jpeg_quality, Pixbuf pixbuf)
481
SaveToSuitableFormat (destination, pixbuf, jpeg_quality);
483
using (var metadata_from = Metadata.Parse (source)) {
484
using (var metadata_to = Metadata.Parse (destination)) {
485
metadata_to.CopyFrom (metadata_from);
491
private static void SaveToSuitableFormat (SafeUri destination, Pixbuf pixbuf, uint jpeg_quality)
493
// FIXME: this needs to work on streams rather than filenames. Do that when we switch to
495
var extension = destination.GetExtension ().ToLower ();
496
if (extension == ".png") {
497
pixbuf.Save (destination.LocalPath, "png");
498
} else if (extension == ".jpg" || extension == ".jpeg") {
499
pixbuf.Save (destination.LocalPath, "jpeg", jpeg_quality);
501
throw new NotImplementedException ("Saving this file format is not supported");
507
// This hack below is needed because there is no wrapped version of
508
// Save which allows specifying the variable arguments (it's not
509
// possible with p/invoke).
511
[DllImport("libgdk_pixbuf-2.0-0.dll")]
512
static extern bool gdk_pixbuf_save(IntPtr raw, IntPtr filename, IntPtr type, out IntPtr error,
513
IntPtr optlabel1, IntPtr optvalue1, IntPtr dummy);
515
private static bool Save (this Pixbuf pixbuf, string filename, string type, uint jpeg_quality)
517
IntPtr error = IntPtr.Zero;
518
IntPtr nfilename = GLib.Marshaller.StringToPtrGStrdup (filename);
519
IntPtr ntype = GLib.Marshaller.StringToPtrGStrdup (type);
520
IntPtr optlabel1 = GLib.Marshaller.StringToPtrGStrdup ("quality");
521
IntPtr optvalue1 = GLib.Marshaller.StringToPtrGStrdup (jpeg_quality.ToString ());
522
bool ret = gdk_pixbuf_save(pixbuf.Handle, nfilename, ntype, out error, optlabel1, optvalue1, IntPtr.Zero);
523
GLib.Marshaller.Free (nfilename);
524
GLib.Marshaller.Free (ntype);
525
GLib.Marshaller.Free (optlabel1);
526
GLib.Marshaller.Free (optvalue1);
527
if (error != IntPtr.Zero) throw new GLib.GException (error);