// // Copyright (C) 2008-2009 Jordi Mas i Hernandez, jmas@softcatala.org // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files (the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and/or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions: // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // using System; using System.Text; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Xml.Serialization; using Gdk; using Pango; using Cairo; using Mistelix.Core; using Mistelix.Widgets; using Mistelix.Effects; namespace Mistelix.DataModel { // // Describes an image that is part of a slideshow // public class SlideImage : SlideShowProjectElement.Image { // // Pixel order // // mistelixvideosrc uses RGB pixel order // Cairo uses ARGB (each pixel is a 32-bit quantity, ARGB. The 32-bit quantities are stored native-endian) // Pixbuf uses RGBA (big-endian order) // enum PixelFormat { CAIRO_ARGB, // PIXBUF_RGB, // 3 channels PIXBUF_ARGB // 4 channels }; [XmlIgnoreAttribute] int width, height, stride, channels = 3; [XmlIgnoreAttribute] bool alpha = false; [XmlIgnoreAttribute] byte[] pixels = null; [XmlIgnoreAttribute] Project project; [XmlIgnoreAttribute] int offset_x, offset_y, w, h; [XmlIgnoreAttribute] PixelFormat pixel_format = PixelFormat.PIXBUF_RGB; [XmlIgnoreAttribute] public int Width { get { return width; } } [XmlIgnoreAttribute] public int Height { get { return height; } } [XmlIgnoreAttribute] public int Stride { get { return stride; } } [XmlIgnoreAttribute] public int Channels { get { return channels; } } public SlideImage () { TransitionTime = 2; // Transition from slide to slide (effect duration). Hardcoded default ShowTime = Mistelix.Preferences.GetIntValue (Preferences.DefaultDurationKey); Transition = Mistelix.Preferences.GetStringValue (Preferences.DefaultTransitionKey); Position = (TextPosition) Mistelix.Preferences.GetIntValue (Preferences.DefaultTextPositionKey); } public SlideImage (string image) : this () { if (image == null) throw new ArgumentNullException ("image name cannot be null"); this.image = image; } public SlideImage (string image, string title, int time, string transition) : this () { if (transition == null) throw new ArgumentNullException ("transition cannot be null"); if (image == null) throw new ArgumentNullException ("image name cannot be null"); this.image = image; Title = title; ShowTime = time; Transition = transition; } // Used only for previewing the transition effect public SlideImage (Cairo.ImageSurface img) { if (img.Format != Cairo.Format.Argb32) throw new InvalidOperationException (String.Format ("SlideImage.SlideImage: unsupported format {0}", img.Format)); width = img.Width; height = img.Height; stride = img.Stride; channels = 4; alpha = true; pixels = img.Data; } [System.Xml.Serialization.XmlIgnoreAttribute] public byte[] Pixels { get { if (pixels == null) LoadSlide (); return pixels; } set { pixels = value; } } public void LoadSlide () { if (pixels != null) return; LoadAndScaleImage (); ProcessEffects (); ProcessImage (); } [System.Xml.Serialization.XmlIgnoreAttribute] public Project Project { set { project = value; } get { return project; } } public void CopyProperties (SlideImage src) { // TODO: Missing SlideImage properties width = src.width; height = src.height; stride = src.stride; alpha = src.alpha; channels = src.channels; Effects = src.Effects; project = src.project; Position = src.Position; image = src.image; } // The list of images for a slideshow is kept on a list // When generating the slideshow the pixels are allocated, however they are only // needed during the slideshow generation. Releasing the pixels helps to GC // to free the memory used public void ReleasePixels () { pixels = null; } public SlideImage GetSlideThumbnail(int width, int height) { return GetSlideThumbnail(width, height, false); } public SlideImage GetSlideThumbnail(int width, int height, bool copyTitle) { SlideImage newImage = new SlideImage(); newImage.CopyProperties(this); using(DataImageSurface thumbnail = this.GetThumbnail(width, height, copyTitle)) { newImage.FromDataImageSurface(thumbnail); } return newImage; } public DataImageSurface GetThumbnail (int width, int height) { return GetThumbnail(width, height, false); } public DataImageSurface GetThumbnail (int width, int height, bool copy_title) { PixelFormat pixelformat_src; SlideImage slide = new SlideImage (); slide.CopyProperties (this); if(copy_title) slide.Title = Title; slide.LoadAndScaleImage (width, height); slide.ProcessEffects (); slide.ProcessImage (); pixelformat_src = slide.pixel_format; // Pixel format of current buffer slide.pixel_format = PixelFormat.CAIRO_ARGB; // New target format slide.LoadFromPixelData (slide.pixels, pixelformat_src, width, height, width * slide.channels, slide.channels); return new DataImageSurface (DataImageSurface.Allocate (slide.Pixels), Cairo.Format.ARGB32, slide.width, slide.height, slide.stride); } public DataImageSurface ToDataImageSurface() { PixelFormat pixelformat_src; SlideImage slide = new SlideImage (); slide.CopyProperties (this); slide.pixels = (byte[]) this.Pixels.Clone(); slide.ProcessEffects (); pixelformat_src = slide.pixel_format; // Pixel format of current buffer slide.pixel_format = PixelFormat.CAIRO_ARGB; // New target format slide.LoadFromPixelData (slide.pixels, pixelformat_src, width, height, width * slide.channels, slide.channels); return new DataImageSurface (DataImageSurface.Allocate (slide.Pixels), Cairo.Format.ARGB32, slide.width, slide.height, slide.stride); } // mistelixvideosrc expects images in 24 bits (3 channels) public void FromDataImageSurface (DataImageSurface img) { if (img.Format != Cairo.Format.Argb32) throw new InvalidOperationException (String.Format ("SlideImage.FromCairo: unsupported format {0}", img.Format)); width = img.Width; height = img.Height; pixels = img.Get24bitsPixBuf (); channels = 3; stride = ((img.Width * channels) + 3) & ~3; alpha = false; } void DrawImageLegend (Cairo.Context cr, string title, int x, int y, int width, int height) { const int marginx = 50; // Distance of the coloured box from the x margins (in safe area) const int marginy = 50; // Distance of the coloured box from the y margins (in safe area) const int textoffset_x = 5; // Offset where the text is drawn const int textoffset_y = 5; // Offset where the text is drawn int box_x, box_y, box_w, box_h; int w, h; int max_width; if (title == null) return; using (Pango.Layout layout = Pango.CairoHelper.CreateLayout (cr)) { layout.SingleParagraphMode = false; max_width = width - ((marginx + textoffset_x) * 2); layout.Width = (int) (max_width * Pango.Scale.PangoScale); layout.FontDescription = FontDescription.FromString (project.Details.SlideshowsFontName); layout.SetText (title); layout.GetPixelSize (out w, out h); w = w + textoffset_x * 2; box_x = x + ((width - (marginx * 2) - w) /2); box_x += marginx; box_w = w; box_h = h + textoffset_y * 2; switch (Position) { case TextPosition.Top: box_y = y + marginy; break; case TextPosition.Bottom: default: box_y = y + height - marginy - h; break; } // Background cr.Color = project.Details.SlideshowsBackColor; cr.Rectangle (box_x, box_y, box_w, box_h); cr.Fill (); cr.Stroke (); cr.MoveTo (box_x + textoffset_x, box_y + textoffset_y); cr.Color = project.Details.SlideshowsForeColor; Pango.CairoHelper.ShowLayout (cr, layout); } } void LoadAndScaleImage () { if (project == null) throw new InvalidOperationException (String.Format ("SlideImage.CreateImage: need project defined (image {0})", image)); LoadAndScaleImage (project.Details.Width, project.Details.Height); ProcessEffects (); } // // Loads the image from disk and scales it to certain size // It is used to generate the final images and thumbnails // void LoadAndScaleImage (int width, int height) { if (image == null) throw new InvalidOperationException ("SlideImage.LoadAndScaleImage: no filename defined for image"); if (width <= 0 || height <= 0) throw new InvalidOperationException ("SlideImage.LoadAndScaleImage: width and height should be > 0"); Logger.Debug ("SlideImage.LoadAndScaleImage. {0} {1} {2}", image, width, height); int max_w = width; // max target width int max_h = height; // max target height double target_ratio = (double) max_w / (double) max_h; // aspect ratio target double scale, original_ratio, corrected_ratio; Gdk.Pixbuf raw_image, processed_image; raw_image = new Gdk.Pixbuf (image); Logger.Debug ("SlideImage.LoadAndScaleImage. Load image w:{0} h:{1} channels:{2}", raw_image.Width, raw_image.Height, raw_image.NChannels); if (raw_image.NChannels != 3 && raw_image.NChannels != 4) { Logger.Error ("SlideImage.LoadAndScaleImage. Image {0} with unsupported number of channels ({1})", image, raw_image.NChannels); return; } processed_image = new Gdk.Pixbuf (Colorspace.Rgb, raw_image.NChannels == 3 ? false : true, 8, max_w, max_h); processed_image.Fill (0x00000000); original_ratio = (double) raw_image.Width / (double) raw_image.Height; // Image is larger that target resolution, we need to rescale if (raw_image.Width > max_w || raw_image.Height > max_h) { if (original_ratio < 1) { // If X is properly scaled (the smaller), Y will be too if (original_ratio > target_ratio) corrected_ratio = target_ratio / original_ratio; else corrected_ratio = original_ratio / target_ratio; scale = (double) max_w / (double) raw_image.Width; h = (int) ((double) raw_image.Width * scale / original_ratio * corrected_ratio); w = (int) ((double) raw_image.Width * scale * corrected_ratio); } else { // If Y is properly scaled (the smaller), X will be too (used path for NTSC and PAL resolutions) if (original_ratio > target_ratio) corrected_ratio = target_ratio / original_ratio; else corrected_ratio = original_ratio / target_ratio; scale = (double) max_h / (double) raw_image.Height; h = (int) ((double) raw_image.Width * scale / original_ratio * corrected_ratio); w = (int) ((double) raw_image.Width * scale * corrected_ratio); } } else { // No need to scale w = raw_image.Width; h = raw_image.Height; } if (w < max_w) offset_x = (max_w -w) / 2; else offset_x = 0; if (h < max_h) offset_y = (max_h - h) / 2; else offset_y = 0; raw_image.Scale (processed_image, offset_x, offset_y, w, h, offset_x, offset_y, (double) w / (double) raw_image.Width, (double)h /(double) raw_image.Height, InterpType.Hyper); LoadFromPixelData (processed_image.Pixels, processed_image.NChannels == 3 ? PixelFormat.PIXBUF_RGB : PixelFormat.PIXBUF_ARGB, processed_image.Width, processed_image.Height, processed_image.Rowstride, processed_image.NChannels); raw_image.Dispose (); processed_image.Dispose (); } void ProcessImage () { Logger.Debug ("SlideImage.ProcessImage. {0} Channels {1}", image, Channels); if (Title == null || Title == string.Empty) return; Logger.Debug ("SlideImage.ProcessImage. Image {0}", image); byte[] pix; if (channels == 3) { int src = 0; byte [] source; int stride_trg = 4 * width; int len_trg = stride_trg * height; source = Pixels; pix = new byte [len_trg]; for (int trg = 0; trg < len_trg; trg = trg + 4) { pix [trg] = source [src + 2]; pix [trg + 1] = source [src + 1]; pix [trg + 2] = source [src + 0]; pix [trg + 3] = 0xff; src += 3; } } else pix = Pixels; DataImageSurface surface = new DataImageSurface (DataImageSurface.Allocate (pix), Cairo.Format.Argb32, width, height, stride); Cairo.Context gr = new Cairo.Context (surface); DrawImageLegend (gr, Title, offset_x, offset_y, w, h); FromDataImageSurface (surface); ((IDisposable)gr).Dispose (); ((IDisposable)surface).Dispose (); } public void ProcessEffects () { SlideImage processed; Effect effect; if (Effects == null) return; processed = this; foreach (string name in Effects) { Logger.Debug ("SlideImage.ProcessEffects. Effect {0}", name); effect = EffectManager.CreateFromName (name); processed = effect.Apply (processed); } Pixels = processed.Pixels; } void LoadFromPixelData (IntPtr data, PixelFormat format_src, int width_src, int height_src, int stride_src, int channels_src) { int len = stride_src * height_src; byte[] source = new byte [len]; Marshal.Copy (data, source, 0, len); LoadFromPixelData (source, format_src, width_src, height_src, stride_src, channels_src); } void LoadFromPixelData (byte[] source, PixelFormat format_src, int width_src, int height_src, int stride_src, int channels_src) { if ((pixel_format == PixelFormat.CAIRO_ARGB && pixel_format == PixelFormat.PIXBUF_RGB) || (channels_src != 3 && channels_src != 4)) { throw new InvalidOperationException ( String.Format ("Could not process SlideImage.LoadFromPixelData requested format {0} image {1}", pixel_format, channels_src)); } int src, len; Logger.Debug ("SlideImage.LoadFromPixelData. f:{0} w:{1} h:{2} s:{3} c:{4}", format_src, width_src, height_src, stride_src, channels_src); switch (pixel_format) { case PixelFormat.PIXBUF_RGB: alpha = false; channels = 3; break; case PixelFormat.PIXBUF_ARGB: case PixelFormat.CAIRO_ARGB: channels = 4; alpha = true; break; default: throw new InvalidOperationException ("Unsupported format"); } // Target data array width = width_src; height = height_src; stride = ((width * channels) + 3) & ~3; len = stride * height; pixels = new byte [len]; src = 0; for (int trg = 0; trg < len; trg = trg + channels) { if (pixel_format == PixelFormat.CAIRO_ARGB) { pixels [trg] = source [src + 2]; pixels [trg + 1] = source [src + 1]; pixels [trg + 2] = source [src + 0]; } else { pixels [trg] = source [src]; pixels [trg + 1] = source [src + 1]; pixels [trg + 2] = source [src + 2]; } if (channels == 4) pixels [trg + 3] = 0xff; src += channels_src; } } } }