Showing posts with label GIMP. Show all posts
Showing posts with label GIMP. Show all posts

Wednesday, June 3, 2015

How To Produce Animated GIFs from C# (Partially)

For some prior and upcoming blog posts, I have added animated GIFs. The images in question were diagrams, which were easy to generate using a C# function, given a Graphics context. I foresaw the need to do this more often, and so I encapsulated the functionality into a useful abstract class:

using System;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media.Imaging;


/// <summary>
/// Encapsulates an algorithm for generating an animated GIF image stream from a
/// collection of dynamically-generated image frames.
/// </summary>
public abstract class GifGenerator
{
 #region Abstractions

 /// <summary>
 /// The width, in pixels, of the output image.
 /// </summary>
 public abstract int Width { get; }

 /// <summary>
 /// The height, in pixels, of the output image.
 /// </summary>
 public abstract int Height { get; }

 /// <summary>
 /// The numer of frames in the animation.
 /// </summary>
 public abstract int FrameCount { get; }

 /// <summary>
 /// Callback to render a frame of animation.
 /// </summary>
 /// <param name="g">The target graphics context upon which to render the frame.</param>
 /// <param name="frameIndex">The frame index of the animation. Ranges from 0 to FrameCount - 1.</param>
 protected abstract void RenderFrame(Graphics g, int frameIndex);

 #endregion


 #region Public methods

 /// <summary>
 /// Writes the GIF image stream of this GifGenerator to the specified filename.
 /// </summary>
 /// <param name="outputFilename">The filename of the resulting file.</param>
 public void Generate(string outputFilename)
 {
  using (var file = File.Create(outputFilename))
   Generate(file);
 }

 /// <summary>
 /// Writes the GIF image stream of this GifGenerator to the specified stream.
 /// </summary>
 /// <param name="output">The stream into which the result will be written.</param>
 public void Generate(Stream output)
 {
  ProduceBitmapEncoder().Save(output);
 }

 #endregion


 #region Helper methods

 GifBitmapEncoder ProduceBitmapEncoder()
 {
  var result = new GifBitmapEncoder();

  using (var buffer = new Bitmap(Width, Height))
   foreach (var frameIndex in Enumerable.Range(0, FrameCount))
   {
    RenderFrameToBuffer(buffer, frameIndex);
    AppendBufferToEncoder(buffer, result);
   }

  return result;
 }

 void RenderFrameToBuffer(Bitmap buffer, int frameIndex)
 {
  using (var g = Graphics.FromImage(buffer))
   RenderFrame(g, frameIndex);
 }

 static void AppendBufferToEncoder(Bitmap buffer, BitmapEncoder encoder)
 {
  var bitmapSource = Imaging.CreateBitmapSourceFromHBitmap(buffer.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
  var bitmapFrame = BitmapFrame.Create(bitmapSource);
  encoder.Frames.Add(bitmapFrame);
 }

 #endregion
}

The class itself is easy to use. You need only inherit from it, implement its abstract properties to specify the output GIF size, and implement its RenderFrame method to draw each image that will make up the GIF. As an example, I wrote a program that animates a bouncing ball:

using System.Drawing;
using System.Drawing.Drawing2D;


public static class Program
{
 class BouncyBallGifGenerator : GifGenerator
 {
  const int BallDiameter = 60;

  public override int Width { get { return BallDiameter; } }
  public override int Height { get { return 100 + BallDiameter; } }
  public override int FrameCount { get { return 20; } }

  protected override void RenderFrame(Graphics g, int frameIndex)
  {
   g.SmoothingMode = SmoothingMode.AntiAlias;
   g.Clear(Color.CornflowerBlue);
   g.FillEllipse(Brushes.Orange, new Rectangle(0, BallHeightForFrame(frameIndex), BallDiameter-1, BallDiameter-1));
   g.DrawEllipse(Pens.Black, new Rectangle(0, BallHeightForFrame(frameIndex), BallDiameter-1, BallDiameter-1));
  }

  static int BallHeightForFrame(int frameIndex) { return (frameIndex - 10) * (frameIndex - 10); }
 }

 public static void Main()
 {
  new BouncyBallGifGenerator().Generate(@"C:\output.gif");
 }
}

There is one problem, however: the resulting GIF does not have its animation properties specified. If you open it with a web browser in its current form, you will probably find it to render with a high frame duration, and no looping. This is unacceptable, because the only reason anyone would animate a GIF is to loop the animation.

I tried, and failed, to find an answer to this problem using Google. The WPF GifBitmapEncoder isn't configured out-of-the-box to allow you to set the animation properties of the output GIF. The WPF team at Microsoft really only created it to open GIF images for display in WPF applications, not as a general-purpose GIF toolkit. There are probably libraries available which will write your GIF with animation properties, but I wanted to stick with the core libraries that would be installed anywhere on earth.

Fortunately, I found a way to hand-edit the resulting images in Gimp. Take the output from the above program and open it in Gimp. Run the 'File → Export...' menu command, and choose an output filename (I just overwrite the original). On the subsequent "Export Image as GIF" dialog, check the 'As animation', 'Loop forever' and 'Use delay entered above for all frames' checkboxes. Set the 'Delay between frames where unspecified' value as desired; your resulting framerate will be (1000/delay) frames per second. Hit the export button, and you will get the output you desire:

One other important point: the acronym is pronounced GIF (with a hard G), not JIF. I don't care what Steve Wilhite says. If he wants me to pronounce it as JIF, he must rename the format to Giraffics Interchange Format, complete with long-necked images roaming the African savannah, eating choice leaves from the top of the tallest trees.

Wednesday, July 23, 2014

Sand Tiles

Now that we have some water tiles, we should create some sand tiles to go with them. That way, we can have a beach!

  • Create a new image, and fill it with yellowish brown.
  • Add a new layer, and set its blend mode to 'addition'. Fill it with height-16 diagonal noise, and apply a Gaussian blur of 16x8 pixels with RLE set.
  • Equalize your blurred noise, and adjust the levels so that the output range is 0 to 64. This first layer will give you some sand dunes.
  • Add a second new layer, and set its blend mode to 'addition'. Fill it with height-16 diagonal noise. Equalize it, and adjust the levels so that the output range is small (I used 0 to 16). This layer will give you some sandy grain.
  • (Optional)Reduce your colour depth so that your sand is only eight colours. (See here for details.)

The result is some golden dunes:

Monday, July 21, 2014

Water Tiles

The last few posts have been about scripting GIMP. Let's put that scripting to use and create some soothing ocean water.

  • Create a new image, and fill it with lightish blue.
  • Add a new layer, and set its blend mode to 'addition'. Fill it with height-16 diagonal noise, and apply a Gaussian blur of 8x4 pixels with RLE set.
  • Equalize your blurred noise, and apply a colour curve. You want the curve to look something like an inverted V, with a sharp point at the middle, fading to black on either extreme. This will hopefully give you a few disconnected sparkly pixels, and some rippling waves. (You may need to regenerate the noise a few times in order to get a sample that produces good results.)
  • (Optional)Merge your visible layers, and set your image mode to indexed. Use an optimum eight-colour palette. Then turn your image mode back to RGB. Reducing the colour depth is a matter of personal taste. I do it because I want my tiles to have a distinct, sixteen-colors-per-8x8-tile look that you would get on SNES hardware. I use eight colours for the water because I will eventually need to create more tiles that allow me to seamlessly connect water tilse to other tile types. More on that later.

The procedure outlined can lead you to water nice enough looking to want to swim in:

Friday, July 18, 2014

GIMP Script Fu: Rectangular Grid (also, Scheme vectors)

Here is another quick script I whipped up for The GIMP. This one takes a width and a height, and draws you a grid with rectangles of the specified size.

The most interesting thing about the script is not what it does, but how it handles vectors. What little GIMP documentation I could find tells me to use cons-array and aset to build up an array for the gimp-pencil function. This offended my delicate sensibilities. Surely I can build a literal to pass into the function, and not have to generate the array iteratively! It turns out I can. If you hashtag something, it will build you a vector that gimp-pencil expects.

The script also demonstrates the use of Scheme's backquote operator. My vector is not actually fully constant; it depends on the input width/height. You cannot construct a vector directly like you would want to, because instead of containing the evaluated value of (- width 1), it will contain the literal closure of (- width 1). I am probably maligning the technical aspect, but suffice it to say, if you put a backquote before your vector literal, and a comma before the functions you want to evaluate before building the vector, it does The Right Thing (TM).

(define
 (script-fu-rectangle-grid image layer width height)
 (let*
  (
  )
  
  ; Input validation ;
  (set! width (max width 3))
  (set! height (max height 3))
  
  (gimp-context-push)
  (gimp-undo-push-group-start image)
  
  ; Implementation goes here ;
  
  (gimp-layer-resize-to-image-size layer)  
  (gimp-layer-resize layer width height 0 0)
  
  (gimp-layer-add-alpha layer)
  (gimp-drawable-fill layer TRANSPARENT-FILL)
  
  (gimp-context-set-brush "Pixel (1x1 square)")
  (gimp-context-set-foreground '(0 0 0))   
  
  (gimp-pencil layer 10 `#(
    0            0
    0            ,(- height 1)
    ,(- width 1) ,(- height 1)
    ,(- width 1) 0
    0            0
   )
  )
  (plug-in-tile RUN-NONINTERACTIVE image layer (car (gimp-image-width image)) (car (gimp-image-height image)) FALSE)
  
  (gimp-layer-set-opacity layer 20)
  (gimp-layer-set-mode layer NORMAL-MODE)
  
  (gimp-undo-push-group-end image)
  (gimp-context-pop)
  (gimp-displays-flush)
 )
)

(script-fu-register
 "script-fu-rectangle-grid"
 "Grid"
 "Sets the contents of a layer to be a semi-transparent grid of rectangles."
 "The Unary Heap"
 "The Unary Heap"
 "2014-07-16"
 "RGB*"

 SF-IMAGE      "Image"             0
 SF-DRAWABLE   "Drawable"          0
 SF-VALUE      "Width"            "16"
 SF-VALUE      "Height"            "8"
)

(script-fu-menu-register
 "script-fu-rectangle-grid"
 "<Image>/Filters/UnaryHeap"
)

I'm not including a screenshot of the output of this one; it's pretty boring.

Wednesday, July 16, 2014

GIMP Script Fu: Tileable Noise

Many techniques in image processing work off of noise: randomized pixel values that you can add to an image, subtract from an image, blend into an image, or otherwise use as the grist for some image filter. Creating noise for use in creating a tileset is no different, but it has certain requirements.

I am creating 32x16 diamond, and I want them to join seamlessly. I cannot tile a patch of 16x16 noise, because that would repeat in the space of a single diamond. I cannot tile a patch of 32x16 noise, bececause that would only repeat every other tile (imagine a black-and-white checker board pattern). What I really need is a diamond-shaped patch of noise.

It so happens that I can produce the results I want by starting with a 16x16 patch of noise, and then doubling it to 32x16, and then skewing it, and then looping it back on itself, like so:

Creating such 'tileable' noise was something I saw myself needing to do so regularly that I created a GIMP script to automate the process. It takes a single parameter (the height of the noise rectangle to generate), generates a rectangle using the above process (strictly, using a process that gives equivalent results), and tiles it to the image size. The source code is below:

(define
 (script-fu-diagonal-noise image layer height)
 (let*
  (
   (width (* 2 height))
  )
  (gimp-context-push)
  (gimp-undo-push-group-start image)
  
  ; Implementation start ;
  
  (gimp-layer-resize-to-image-size layer)
  
  ; Produce a square patch of noise ;
  (gimp-image-select-rectangle image CHANNEL-OP-REPLACE 0 0 (/ width 2) height)
  (plug-in-randomize-hurl RUN-NONINTERACTIVE image layer 100.0 1.0 TRUE 0)
  (plug-in-colors-channel-mixer RUN-NONINTERACTIVE image layer FALSE 1.0 0.0 0.0 1.0 0.0 0.0 1.0 0.0 0.0)
  
  ; Copy top left to bottom right ;  
  (gimp-image-select-rectangle image CHANNEL-OP-REPLACE 0 0 (/ width 2) (/ height 2))
  (gimp-edit-copy layer)
  (let* ((floater (car (gimp-edit-paste layer FALSE))))
   (gimp-layer-translate floater (/ width 2) (/ height 2))
   (gimp-floating-sel-anchor floater)
  )
  
  ; Copy bottom left to top right ;
  (gimp-image-select-rectangle image CHANNEL-OP-REPLACE 0 (/ height 2) (/ width 2) (/ height 2))
  (gimp-edit-copy layer)
  (let* ((floater (car (gimp-edit-paste layer FALSE))))
   (gimp-layer-translate floater (/ width 2) (/ height -2))
   (gimp-floating-sel-anchor floater)
  ) 
  
  ; Clip layer to noise size, tile it, and reset the alpha channel ;
  (gimp-layer-resize layer width height 0 0)
  (plug-in-tile RUN-NONINTERACTIVE image layer (car (gimp-image-width image)) (car (gimp-image-height image)) FALSE)
  (gimp-layer-flatten layer)
  
  ; Implemenation end ;
  
  (gimp-undo-push-group-end image)
  (gimp-context-pop)
  (gimp-displays-flush)
 )
)

(script-fu-register
 "script-fu-diagonal-noise"
 "Diagonal Noise"
 "Fills a layer with uniformly-distributed grayscale noise that has a repeating diagonal grain. The period of the noise's repeating pattern will be twice as large as it is high."
 "The Unary Heap"
 "The Unary Heap"
 "2014-07-15"
 "RGB*"

 SF-IMAGE      "Image"             0
 SF-DRAWABLE   "Drawable"          0 
 SF-VALUE      "Height"            "16"
)

(script-fu-menu-register
 "script-fu-diagonal-noise"
 "<Image>/Filters/UnaryHeap"
)

If you run the script, you will get results similar to the following:

Tuesday, July 15, 2014

GIMP Script Fu Templates

I have spent the last couple of days hacking on The GIMP's scripting engine. It's a bit tricky to pick up, since the developers of GIMP chose to use Scheme (a Lisp variant) as their scripting language. That didn't bother me too much. However, their documentation on scripting isn't that great. This post is to hopefully help some poor soul who has to write their own GIMP scripts.

There are two kingdoms of scripts for The GIMP: standalone and image-dependent. A standalone script starts with no image, collects some user parameters (or not), and is expected to produce a new image. An image-dependent script requires an open image to process, collects some user parameters (or not), and is expected to modify the selected layer.

A bare-bones standalone script looks like the following:

(define
 (script-fu-template-standalone)
 (let*
  (
   (image (car (gimp-image-new 256 256 RGB)))
  )
  
  (gimp-context-push)
  (gimp-image-undo-disable image)
  
  ; Implementation goes here
  
  (gimp-image-undo-enable image)
  (gimp-context-pop)
  
  (gimp-image-clean-all image)
  (gimp-display-new image)
 )
)

(script-fu-register
 "script-fu-template-standalone"
 "MENU LABEL"
 "DESCRIPTION"
 "AUTHOR"
 "COPYRIGHT"
 "CREATION DATE"
 "" ; empty string because script creates a new image
)

(script-fu-menu-register
 "script-fu-template-standalone"
 "<Image>/File/Create/GROUPING"
)

A bare-bones image-dependent script looks like the following:

(define
 (script-fu-template-image-dependent image layer)
 (let* ()
  (gimp-context-push)
  (gimp-undo-push-group-start image)
  
  ; Implementation goes here ;
  
  (gimp-undo-push-group-end image)
  (gimp-context-pop)

  (gimp-displays-flush)
 )
)

(script-fu-register
 "script-fu-template-image-dependent"
 "MENU LABEL"
 "DESCRIPTION"
 "AUTHOR"
 "COPYRIGHT"
 "CREATION DATE"
 "RGB*"

 SF-IMAGE      "Image"             0
 SF-DRAWABLE   "Drawable"          0 
)

(script-fu-menu-register
 "script-fu-template-image-dependent"
 "<Image>/Filters/GROUPING"
)

Additional parameters can be passed into the end of the script-fu-register function call. If you do so, you also need to declare them in the definition of your script method. A list of parameter types can be found here.

The GIMP has a handy command to list all available scripting functions under its 'Help -> Procedure Browser' menu. It will even list any custom scripts you have created, so be sure to fill in the description, author, copyright, and creation date strings.

During development of your scripts, when you are frequently making changes, you will need to run the 'Filters -> Script-Fu -> Refresh Scripts' menu command. This will cause The GIMP to grab your latest changes so that the next time you run your script it will be running the latest version.

Happy scripting!