Cropping

Jun 25, 2014 at 6:14 PM
Heya! I've been using a batch file for some simple cropping of a bunch of images into smaller chunks for a project and I recently decided to not use a batch file and use C# because it allows me to have a lot more flexibility and customization. In the batch file I was using this line.
REM size of chunks
set SIZEX=1280
set SIZEY=720

REM amount of times left and up from the origin tile.
set OFFSETX=-1
set OFFSETY=-1

REM del "output\*.png"
convert "*.png" -crop %SIZEX%x%SIZEY% -set filename:tile "%%t %%[fx:page.x/%SIZEX%+%OFFSETX%]_%%[fx:page.y/%SIZEY%+%OFFSETY%]" "output\%%[filename:tile].png"
The convert line specifically, is there a simple way to do that using the .NET MagickImage framework? Or will I have to create the loops myself? Thanks. :)
Coordinator
Jun 30, 2014 at 6:56 AM
This is not yet possible with the current release. I will post an example after I have published the next release.
Jun 30, 2014 at 7:35 AM
I actually got around it using the MagickImage crop and some cloning. :)
var data = imageData[i];
var files = Directory.GetFiles(imageOutputDirectory + "\\", data.filename.Substring(0, data.filename.IndexOf(".")) + " *.png");
foreach (var file in files)
{
    File.Delete(file);
}

var img = new MagickImage(data.path);
var horizontalChunks = img.Width/data.chunkSize.X;
var verticalChunks = img.Height/data.chunkSize.Y;
var transparent = new MagickImage(new MagickColor(Color.Transparent), data.chunkSize.X, data.chunkSize.Y);

for (int x = -data.positionalOffset.X; x < horizontalChunks - data.positionalOffset.X; x++)
{
    for (int y = -data.positionalOffset.Y; y < verticalChunks - data.positionalOffset.Y; y++)
    {
        var clone = img.Clone();
        clone.Crop(new MagickGeometry((x + data.positionalOffset.X) * data.chunkSize.X, (y + data.positionalOffset.Y) * data.chunkSize.Y, data.chunkSize.X, data.chunkSize.Y));
        if(!clone.Equals(transparent))
            clone.Write(imageOutputDirectory + "\\" + data.filename.Substring(0, data.filename.IndexOf(".")) + " " + x + "_" + y + ".png");
        }
}
Coordinator
Jul 6, 2014 at 5:48 PM
With the latest version of Magick.NET your batch file roughly translates to this:
string outputDirectory = @"c:\output";

int sizeX = 1280; // set SIZEX=1280
int sizeY = 720; // set SIZEY=720

int offsetX = -1; //set OFFSETX=-1
int offsetY = -1; //set OFFSETY=-1

// del "output\*.png"
foreach (string fileName in Directory.GetFiles(outputDirectory, "*.png"))
{
  File.Delete(fileName);
}

// convert "*.png"
foreach (string fileName in Directory.GetFiles(@"c:\input", "*.png"))
{
  using (MagickImage source = new MagickImage(fileName))
  {
    foreach (MagickImage image in source.CropToTiles(sizeX, sizeY)) // -crop %SIZEX%x%SIZEY%
    {
      // -set filename:tile "%%t %%[fx:page.x/%SIZEX%+%OFFSETX%]_%%[fx:page.y/%SIZEY%+%OFFSETY%]"
      // This does not do the -set operation but it is easier to just put filename:tile in a variable.
      string name = image.FormatExpression(string.Format("%t %[fx:page.x/{0}+{1}]_%[fx:page.y/{2}+{3}]", sizeX, offsetX, sizeY, offsetY));

      // "output\%%[filename:tile].png"
      image.Write(outputDirectory + "\\" + name + ".png");
    }
  }
}
Sep 10, 2015 at 12:39 PM
Edited Sep 10, 2015 at 12:40 PM
Hi, I have a similar problem. I need to load a huge image and convert it to slightly overlapping tiles. Is there a better way, than cloning the original image and crop it? It would be great to have some function like MagickImage GetSubImage(MagickImage image, MagickGeometry region), that returns a subimage without destroying the original image. Is there something similar? Thanks

pet
Coordinator
Sep 10, 2015 at 12:55 PM
Edited Sep 10, 2015 at 12:55 PM
There are several Clone overloads to do this:
using (MagickImage image = new MagickImage())
{
  using (MagickImage area = image.Clone(10,10,50,50))
  {
  }

  using (MagickImage area = image.Clone(new MagickGeometry(10,10,50,50)))
  {
  }
}
Sep 10, 2015 at 12:56 PM
Great! Thank you.
pet
Sep 11, 2015 at 7:41 AM
Edited Sep 11, 2015 at 7:43 AM
Hi! I've tried to replace my code:
using (MagickImage image = new MagickImage(filename)) {
        MagickGeometry gCrop = new MagickGeometry();
        ...

        for(...) {        
            SetGeometry(gCrop...);
            MagickImage clone = image.Clone();
            clone.Crop(gCrop);
            ...
        }
}
by the suggested clone with parameter:
    MagickImage clone = image.Clone(gCrop);
but the result is wrong (black image with some artifacts). Any idea what is wrong? I am using 7.0.0.0018.

Thank you

pet
Coordinator
Sep 11, 2015 at 11:24 AM
I have a unit test for cloning part of an image. I added some extra tests and they all pass. Can you create a small example program and share it on something like dropbox?
Sep 11, 2015 at 12:37 PM
Here is simple example. I probably just missed something, but when I create a clone and crop it afterwards, it is OK. When I use the Clone method with parameters, the image properties (colorspace, depth, etc.) are not cloned.
Bitmap bmp = new Bitmap(4, 4);            
using (var image = new MagickImage(bmp))
{
  image.Format = MagickFormat.Rgba;
  Console.WriteLine(image.ToByteArray().Length); // output: 64

  // ok
  var clone = image.Clone();
  clone.Crop(0, 0, 2, 2);
  Console.WriteLine(clone.ToByteArray().Length); // output: 16

  // fail
  var croppedclone = image.Clone(0, 0, 2, 2);
  Console.WriteLine(croppedclone.ToByteArray().Length);  // null reference 
}
pet
Coordinator
Sep 11, 2015 at 6:34 PM
I can explain why this is happening. When you call the Clone method a 'MagickFormat.Null' image is created with a transparent background and the pixels are copied from the source image to the destination image. And when you call ToByteArray an image with the format 'Null' is written. This is an internal Format that does nothing when you write it and just returns an empty array. So you should call the overload of ToByteArray that requires a MagickFormat.

In the next release of Magick.NET your example will throw an exception instead of just returning an empty array because I will use a different format when the clone is created.
Sep 14, 2015 at 10:47 AM
Thanks for explanation. IMO in this situation, the method should not be called "Clone". There is a huge difference between Clone() and Clone(MagickGeometry geometry). With the second one, you loose the information about color space, depth etc.

pet
Coordinator
Sep 14, 2015 at 10:55 AM
Edited Sep 14, 2015 at 10:55 AM
You are right that this method should not be called Clone if it has this kind of behavior. I will fix this before the next release and make sure all the image information is cloned.
Sep 14, 2015 at 10:57 AM
Thanks.

pet