Crop an image to a square

2

2

In this challenge, you will take an input image and crop it to make a square, leaving only the central part of the input image.

The answers must work with landscape, portrait, or square input images. The width of the output image should be equal to the shortest side of the input image e.g. the result of a 200x300 image should be a 200x200 image. An equal amount must be cropped from both ends of the input image e.g. for a 200x300 input image, 50 pixels should be cropped from each end of the image, leaving only the central part of the image. Where its not possible to crop an equal amount from both sides e.g. a 200x301 image, the extra 1 pixel may be cropped from either end of the image. For a square input image, the output should be identical to the input.

Input:

The input file should be read from the hard disk of the machine running the program. You may choose which of bmp, jpeg, png or pnm to support as the input image format.

Output

The output file should be written to the hard disk of the machine running the program. The format of the output image should be bmp, jpeg, png or pnm.

Win Criteria

This is code golf. Shortest code wins.

Notes:

  1. The input/output file paths do not count towards the program size.
  2. The standard libraries of your programming language may be used. Other libraries are disallowed.
  3. Standard loopholes apply.

Example:

Input Image:

BG1

Expected Output:

BG2

Images by kirill777 - licenced CC-BY 3.0

rdans

Posted 2014-08-05T22:40:53.653

Reputation: 995

3

This seems to have a significant bias toward languages whose standard library contains BMP functions. For example, http://php.net/manual/en/function.imagecreatefromwbmp.php

– Greg Hewgill – 2014-08-05T23:08:11.363

Thats true but allowing external libraries would bring its own set of potential issues and introduce too much scope for loopholes – rdans – 2014-08-05T23:39:10.213

Can we at least choose the image format? – Doorknob – 2014-08-05T23:52:56.810

@doorknob i updated the question to allow jpeg and png. If anyone wants to use another format, please request it in the comments. – rdans – 2014-08-06T00:10:30.350

1PNM? That allows languages with no standard library support for images to participate. – Peter Taylor – 2014-08-06T10:08:26.437

@PeterTaylor added. thanks – rdans – 2014-08-06T12:40:55.173

Answers

4

Parsing the raw bytestream: Haskell (319 = 332 - 13)

Without any imports at all, this program crops P1 PBM images.

g _[]=[]
g z x=(\(a,b)->a:g z b).splitAt z$x
q=map
f=filter
main=do c<-readFile"in.pbm";let(r:p)=tail.f((/='#').head).lines$c;(w:h:_)=(q read).words$r;m=min w h;t s=take m.drop((s-m)`div`2);k d=[[m,m]]++(q(t w)(t h d))in writeFile"out.pbm".("P1\n"++).unlines.q(unwords.(q show)).k.(q((q read).g 1)).take h.g w.f(`elem`"01").concat$p

Special case: helpful formatting (214 = 227 - 13)

My first attempt at PBM (P1) was based on the example at Wikipedia and assumed spaces separated the pixels, and lines in the text correlated with lines in the image. Under those conditions, this shorter version works fine.

main=do c<-readFile"in.pbm";writeFile"out.pbm".("P1\n"++).unlines.q(unwords.(q show)).k.q((q read).words).tail.filter((/='#').head).lines$c
k((w:h:_):d)=let m=min w h;t s=take m.drop((s-m)`div`2)in[[m,m]]++(q(t w)(t h d))
q=map

Bonus feature: strips out comments!

Both of the above work in essentially the same manner as the original below, but with a simpler header, lower bit depth, and no padding, although the topmost program works harder to allow for a range of formatting options.

Also available in a wide range of colours: Haskell (545 = 558 - 13)

No pre-existing image-handling functions were harmed in the making of this program.

Uncompressed 24-bit RGB bitmaps have a fairly straight-forward header system but are complicated by the mod-4 padding for each row's byte array. This code grabs the bitmap offset, width, and height from the input header, calculates the row padding, and extracts the bitmap data into a list of lists (rows of columns, bottom-left corner first).

Cropping is as simple as dropping half the excess and then taking the desired length from each list. The new file size is calculated as 3*(padded width)*height + 54 bytes (for the minimal required headers). The new header is constructed and the cropped image data appended. It's all mapped to characters and written back out to disk.

Input: in.bmp (6 characters removed from total)
Output: out.bmp (7 characters removed from total)

I think there's still some redundancy still in there, but my brain hurts to look at it this way.

import System.IO
import Data.Char
import Data.List.Split
main=do k"in.bmp"ReadMode(\h->do i h u;c<-hGetContents h;let d=l(ord)c;w=s 18d;h=s 22d;p=o w;m=min w h;q=o m;j a b=y a(r b m)in k"out.bmp"WriteMode(\g->do i g u;hPutStr g$"BM"++l chr((z e[m*(3*m+q)+54,0,54,40,m,m,1572865,0,0,1,1,0,0])++((z((++(f q$cycle[0])).y(3*m)(3*r w m)).j m h$chunksOf(3*w+p)$n(s 10d)d)))))
i=hSetBinaryMode
k=withFile
z=concatMap
l=map
v=div
f=take
n=drop
u=True
y t d=f t.n d
s d=sum.zipWith(*)(l(256^)[0..3]).y 4d
o=(`mod`4)
r a b=v(a-b)2
e x=l((`mod`256).(x`v`).(256^))[0..3]

For comparison, an ungolfed version is over 8 times as long, at 4582 (4595 - 13) bytes:

import System.IO
import Data.Char
import Data.List.Split


main = readImage cropImageToSquare


cropImageToSquare fileHandle = do

  hSetBinaryMode fileHandle True
  fileContents <- hGetContents fileHandle

  let
    inImageData         = asBytes fileContents

    inImageBitmapOffset = getIntFromWordAt 10 inImageData
    inImageWidth        = getIntFromWordAt 18 inImageData
    inImageHeight       = getIntFromWordAt 22 inImageData

    inImageBitmapData   = drop inImageBitmapOffset inImageData
    inImageBitmap       = chunksOf (paddedBytesForWidth inImageWidth) inImageBitmapData

    outImageSideLength  = min inImageWidth inImageHeight

    outFileHeader       = fileHeader  outImageSideLength outImageSideLength
    outImageHeader      = imageHeader outImageSideLength outImageSideLength
    outHeaders          = outFileHeader ++ outImageHeader

    outImageBitmap      = crop outImageSideLength outImageSideLength inImageWidth inImageBitmap

    in
      writeImage "out.bmp" outHeaders outImageBitmap


--- Read Image ---------------------------------------------------------

readImage = withFile "in.bmp" ReadMode

--------------------------------------------------------- Read Image ---

--- Write Image --------------------------------------------------------

writeImage filename header imageBitmap = do
  withFile filename WriteMode write'
    where
      write' fileHandle = do
        hSetBinaryMode fileHandle True
        hPutStr fileHandle . ("BM"++)
                           . map chr $ (concatMap wordFromInt header)
                                     ++ (concat imageBitmap)

-------------------------------------------------------- Write Image ---

--- Character-Integer Conversion ---------------------------------------

asBytes = map ord

getWordAt offset = take 4 . drop offset

getIntFromWordAt offset = intFromWord . getWordAt offset

intFromWord     = sum . zipWith (*) (map (256^) [0..3])

wordFromInt int = map((`mod` 256) . (int `div`) . (256^)) [0..3]

--------------------------------------- Character-Integer Conversion ---

--- Headers ------------------------------------------------------------

fileHeader width height = [ fileSize
                          , reserved
                          , bitmapOffset
                          ]
  where
    fileSize     = imageFileSize width height
    reserved     = 0
    bitmapOffset = 54

imageFileSize width height = 54 + height * (paddedBytesForWidth width)
paddedBytesForWidth width  = 3 * width + rowPadding width
rowPadding = (`mod` 4)


imageHeader width height = [ imageHeaderSize
                           , width
                           , height
                           , planesAndBits
                           , compression
                           , bitmapSize
                           , horizontalImageResolution
                           , verticalImageResolution
                           , paletteSize
                           , coloursUsed
                           ]
  where
    imageHeaderSize  = 40
    planesAndBits    = int32FromInt16s colourPlanes bitDepth
    colourPlanes     = 1
    bitDepth         = 24
    compression      = 0
    bitmapSize       = 0
    horizontalImageResolution = pixelsPerMetreFromDPI 72
    verticalImageResolution   = pixelsPerMetreFromDPI 72
    paletteSize      = 0
    coloursUsed      = 0

pixelsPerMetreFromDPI = round . (* (100/2.54))

int32FromInt16s lowBytes highBytes = lowBytes + shift 2 highBytes
shift bytesUp number  = (256 ^ bytesUp) * number

------------------------------------------------------------ Headers ---

--- Crop Image ---------------------------------------------------------

crop toHeight toWidth fromWidth = cropWidth toWidth fromWidth
                                . cropHeight toHeight

cropHeight toHeight image = take toHeight
                          . drop ( halfExtra (length image) toHeight)
                          $ image

cropWidth toWidth fromWidth image = map ( padRow toWidth
                                        . take (3 * toWidth)
                                        . drop (3 * (halfExtra fromWidth toWidth))
                                        )
                                      $ image

padRow toWidth xs = take (paddedBytesForWidth toWidth)
                      $ xs ++ (repeat 0)

halfExtra fromLength toLength = (fromLength - toLength) `div` 2

--------------------------------------------------------- Crop Image ---

comperendinous

Posted 2014-08-05T22:40:53.653

Reputation: 466

Very nice answer. I'm having a little trouble getting it to work with my test images. see https://dl.dropboxusercontent.com/u/141246873/crop/bg1ascii.pbm and https://dl.dropboxusercontent.com/u/141246873/crop/bg1raw.pbm These images are pbm versions of the example image in the question. They were generated using GIMP. One is ascii and the other is raw. Please could you try it with either one of the files. Thanks.

– rdans – 2014-08-06T22:00:46.600

On the first version, some characters can be saved by shortcutting map to an infix function (like (#)=map) instead of q. Maybe on the second version too, i haven't checked. – proud haskeller – 2014-08-07T08:41:10.890

P4 PBMs are out, sorry. As for the P1 image, I followed the Wikipedia example too closely and assumed spaces between pixels and matching line lengths. The new (longer) program I just added handles bg1ascii.pbm, though, and still manages it without importing functionality. – comperendinous – 2014-08-07T11:38:21.313

Tested and its working well with the GIMP generated images – rdans – 2014-08-07T18:22:40.633

3

Rebol, 165  92 (105 - 13)

i: load %in.bmp 
s: i/size
m: min s/1 s/2
c: to-pair reduce[m m]save %out.bmp copy/part skip i s - c / 2 c

At its simplest if you have a image file (in.bmp) of 300x200 then it can be cropped and saved to file (out.bmp) like so:

save %out.bmp copy/part skip load %in.bmp 50x0 200x200

Rebol comes with a spatial coordinates datatype called Pair!  Here are some examples of this datatype (using Rebol console):

>> type? 300x200
== pair!

>> first 300x200
== 300.0

>> second 300x200
== 200.0

>> 300x200 - 200x200
== 100x0

>> 300x200 - 200x200 / 2
== 50x0

The last example shows how the skip coordinates were worked out for balanced cropping.


NB. This solution was tested using latest version of Rebol 3 (see http://rebolsource.net/) on OS X. BMP is currently supported on all platforms. PNG, Jpeg & other formats are only partially implemented (across platforms) at this time.

draegtun

Posted 2014-08-05T22:40:53.653

Reputation: 1 592

3

Mathematica 52

Spaces not needed:

ImageCrop[#, {#, #} &@Min@ImageDimensions@#] &@Import@"c:\\test.png"

Dr. belisarius

Posted 2014-08-05T22:40:53.653

Reputation: 5 345

1

Sorry this is one of the standard loopholes: http://meta.codegolf.stackexchange.com/a/1078/21713

– rdans – 2014-08-08T21:23:42.773

3@Ryan Sorry, but not this time. The OP (you) wrote "The standard libraries of your programming language may be used. Other libraries are disallowed.".I'm using only the standard Mathematica statements - NO LIBRARIES AT ALL – Dr. belisarius – 2014-08-08T21:36:16.317

2

Bash, 115

with ImageMagic, hard to say if you can count it as a standard lib :)

#!/bin/bash
convert $1 -set option:size '%[fx:min(h,w)]x%[fx:min(h,w)]' xc:red +swap -gravity center -composite _$1

Jaa-c

Posted 2014-08-05T22:40:53.653

Reputation: 1 575

I'm not counting it as a standard lib sorry :) – rdans – 2014-08-06T20:21:11.623

2

Java, 373 348 342 336 322

import java.awt.image.*;import java.io.*;import javax.imageio.*;class M{public static void main(String[]a)throws Exception{BufferedImage i=ImageIO.read(new File(a[0]));int[]d={i.getWidth(),i.getHeight()},o={0,0};int s=d[0]>d[1]?1:0;o[1-s]=(d[1-s]-d[s])/2;ImageIO.write(i.getSubimage(o[0],o[1],d[s],d[s]),"bmp",new File("out.bmp"));}}

Ungolfed:

import java.awt.image.*;
import java.io.*;

import javax.imageio.*;

class M{
    public static void main(String[] args) throws Exception{
        // read file as argument, instead of args[0] the path to the file could hardcoded.
        // Since the path to the input file don't count towards the character count, I'm not counting args[0]
        BufferedImage i = ImageIO.read(new File(args[0]));

        int[] dimension = { i.getWidth(), i.getHeight() }, origin = { 0, 0 };

        int smaller = dimension[0] > dimension[1] ? 1 : 0;

        // 1-smaller is the index of the bigger dimension
        origin[1-smaller] = (dimension[1-smaller] - dimension[smaller]) / 2;

        // again, path to output file don't count
        ImageIO.write(i.getSubimage(origin[0], origin[1], dimension[smaller], dimension[smaller]), "bmp", new File("out.bmp"));
    }
}

Usage: java M image.type

Should be able to read jpg, png, bmp, wbmp and gif. Writes a bmp called "o".

Who said you couldn't use Java for golf?

Edit: Just realised that file paths don't count towards the character count.

IchBinKeinBaum

Posted 2014-08-05T22:40:53.653

Reputation: 253

You can use * for imports.. Like import java.io.* etc ;) – Jaa-c – 2014-08-06T17:50:35.630

I'm so used to eclipse handling the imports that I didn't even think of that. There was other stuff that could be removed, too. E: Oops, forgot an import. – IchBinKeinBaum – 2014-08-06T18:02:44.607

You can cut 9 by removing public from the class and the two spaces in String[] a) throws. You should add a golfed (no whitespace) version so it's easier for others to count/verify, though. – Geobits – 2014-08-06T18:55:06.490

@Geobits done. Also apparently I counted too many characters. Should be fixed now. – IchBinKeinBaum – 2014-08-06T19:29:49.967

Works nicely for portrait/landscape images but doesn't seem to produce any output for square images? – rdans – 2014-08-06T20:35:45.940

@Ryan Yes it does. Also I can't see why it would work for portrait but not square as it's exactly the same code that runs. – IchBinKeinBaum – 2014-08-06T22:00:01.980

@IchBinKeinBaum please ignore my comment. I just tried it with this one and it worked: https://dl.dropboxusercontent.com/u/141246873/crop/bg31.jpg not sure what the difference is between this one and the other

– rdans – 2014-08-06T22:16:58.917

2

C# - 250 271 265 229 (incl. 26 for file names)

Forgot about the import for it to work.

using System.Drawing;
class P{static void Main(string[] a){var b=new Bitmap(@"C:\Dev\q.jpg",true);int h=b.Height,w=b.Width,s=h<w?h:w,d=(h-w)/2;b.Clone(new Rectangle(d>0?0:d*-1,d>0?d:0,s,s),b.PixelFormat).Save(@"C:\Dev\q2.jpg");}}

De-golfed (or what you call it).

using System.Drawing;
class Program
{
static void Main(string[] a)
{
var b = new Bitmap(@"C:\Dev\q.jpg",true);
int h=b.Heightw=b.Width,s=h<w?h:w,d=(h-w)/2;
b.Clone(new Rectangle(d>0?0:d*-1,d>0?d:0,s,s),b.PixelFormat).Save(@"C:\Dev\q2.jpg");
}
}

Thanks (again) to w0lf for pointing out different declaration style.

Sander

Posted 2014-08-05T22:40:53.653

Reputation: 121

this can be reduced significantly by erasing extra whitespace and using the int x=..., y=... declaration style: http://pastie.org/9454985

– Cristian Lupascu – 2014-08-08T06:34:29.720

also, you can drop the spaces around equal signs and most line breaks. See the link in my previous comment. It's 232 chars. – Cristian Lupascu – 2014-08-08T08:32:30.400

Thanks! I guess it is better to actually look at the link :) – Sander – 2014-08-08T08:44:59.470

Yes. Well, at both links because I just realized thsi can be shortened even more. :)

– Cristian Lupascu – 2014-08-08T09:14:34.747

Well, according to the rules the image-reference don't count, but yeah, nice! – Sander – 2014-08-08T09:17:22.143

You are right! I had not seen that rule. Then, just the Bitmap -> var improvement counts. – Cristian Lupascu – 2014-08-08T09:24:25.913

Ah, ofcourse! I had changed from var to int on the other values since it didn't matter :) – Sander – 2014-08-08T09:28:33.363

1

C#, 420

If the image is portrait it gets rotated while cropping and then cropped image is rotated to correct orientation.

Edit: now crop centre of the image. Run in terminal as crop.exe a.jpg

class P
{
static void Main(string[] a)
{
if (a.Length > 0)
{
Bitmap b = (Bitmap)Image.FromFile(a[0]);
int w = b.Width;
int h = b.Height;
int d = Math.Abs(w - h) / 2;
if (h > w) { b.RotateFlip(RotateFlipType.Rotate270FlipNone); }
Bitmap c = new Bitmap(h, h);
for (int x = d; x < (h+d); x++)
{
for (int y = 0; y < h; y++)
{
c.SetPixel(x-d, y, b.GetPixel(x, y));
}
}
if (h > w) { c.RotateFlip(RotateFlipType.Rotate90FlipNone); }
c.Save("Q.jpg");
}
}
}

bacchusbeale

Posted 2014-08-05T22:40:53.653

Reputation: 1 235

Unfortunately this throws an exception on an image with portrait orientation. It also outputs the left side of the input image and not the central part. To see what I mean, try it with the example image in the question and compare your result. – rdans – 2014-08-06T19:05:21.087

1

Clojure, 320 (333 - 13 for filenames)

Deciding to sidestep types, this P1 (ASCII) PBM cropper is written in Clojure.

With function names like interpose, read-string, and clojure.string/replace, it's not exactly golf friendly, and I'm not sufficiently experienced to know if I can compile without a namespace declaration. (Leiningen puts it in automatically, and Leiningen is nice to me, so I'm going to leave it there, though I did eject .core.) On the plus side, however, file access and string concatenation are short.

(ns a(:gen-class))(defn -main[](let[[[_ r p]](re-seq
#"(^\s*\d+\s+\d+)([\s\S]*$)"(clojure.string/replace(slurp"in.pbm")#"(P1)|(#.*\n)"""))[w
h](map read-string(re-seq #"\d+"r))c(min w h)](spit"out.pbm"(apply
str"P1\n"c" "c"\n"(flatten(interpose"\n"(take
c(partition c w(drop(+(*(quot(- h c)2)w)(quot(- w c)2))(re-seq #"\S"p))))))))))

The function at the heart of the program is ungolfed below.

(defn crop-pbm [in out]
  (let [         filestream (slurp in)
                 image-data (clojure.string/replace (str filestream "\n") #"(P1)|(#.*\n)" "")
    [[_ resolution pixels]] (re-seq #"(^\s*\d+\s+\d+)([\s\S]*$)" image-data)
                     pixels (re-seq #"\S" pixels)
             [width height] (map read-string (re-seq #"\d+" resolution))
                       crop (min width height)
                 drop-width (- width  crop)
                drop-height (- height crop)
         initial-drop-width (quot drop-width  2)
        initial-drop-height (quot drop-height 2)
               initial-drop (+ (* initial-drop-height width) initial-drop-width)
             cropped-pixels (take crop
                                  (partition crop width
                                             (drop initial-drop pixels)))
              cropped-image (apply str "P1\n" crop " " crop "\n"
                                       (flatten (interpose "\n" cropped-pixels)))]
      (spit out cropped-image)))

Rather than constructing a two-dimensional array to represent the image, this program calculates the number of pixels to the new top-left corner and drops them, then jumps original-width pixels at a time, taking cropped-side-length pixels from as many steps.

The regular expression function re-seq does much of the heavy lifting, stripping out whitespace and forming arrays simultaneously, which leads me to suspect that Perl could follow this approach and yield a shorter program.

comperendinous

Posted 2014-08-05T22:40:53.653

Reputation: 466

1

Perl, 276 287 (289 300 - 13)

Regex to the Rescue!

Who needs dedicated image functions when you have regular expressions? (And 1-bit images to crop.)

local$/;open I,"in.pbm";$_=<I>;s/(P1)|(#.*\n)//g;/^\s*(\d+)\s+(\d+)([\s\S]*)$/;($w,$h,$_)=($1,$2,$3);$c=$w<$h?$w:$h;s/\D//g;$d=$w-$c;while($z++<$w*int(($h-$c)/2)+int($d/2)){s/^\d//};while($i++<$c){/^(\d{$c})(\d{$d})(.*)/;$p.=$1."\n";$_=$3."0"x$w}open O,'>',"out.pbm";print O"P1\n$c $c\n$p"

That's the golfed version of:

use strict;
use warnings;

# Read in the image file
local $/ = undef;

open IMAGEIN, '<', "in.pbm" or die "Can't read 'in.pbm': $!";
my $image_data = <IMAGEIN>;
close IMAGEIN;

# Remove the header and any comments
$_ = $image_data;
s/(P1)|(#.*\n)//g;

# Divide into width, height, and pixels
/^\s*(\d+)\s+(\d+)([\s\S]*)$/;
(my $width, my $height, $_) = ($1, $2, $3);

# Remove anything that isn't a number from the pixel data
s/\D//g;
my $pixels = $_;

# Determine the new dimensions
my $crop = $width < $height ? $width : $height;

# Calculate total to remove along each axis
my $drop_width  = $width  - $crop;
my $drop_height = $height - $crop;

# Calculate how much to remove from the first side along each axis
my $initial_drop_width  = int($drop_width  / 2);
my $initial_drop_height = int($drop_height / 2);

# Calculate total pixels to the new top-left corner
my $initial_drop = $width * $initial_drop_height + $initial_drop_width;

# Remove the pixels preceding the corner
$_ = $pixels;
while (32760 < $initial_drop)  # Stay under regex limit
{
    s/^\d{32760}//;
    $initial_drop -= 32760;
}
s/^\d{$initial_drop}//;

# Take *crop* rows of *crop* pixels
$pixels = "";
for (my $i=0; $i<$crop; $i++)
{
    /^(\d{$crop})(\d{$drop_width})(.*)/;
    $pixels .= $1 . "\n";
    $_ = $3 . "0"x$width;  # Add some 0s to ensure final match
}

# Construct the new, cropped image
my $cropped_image = "P1\n$crop $crop\n$pixels";

# Write out the image file
open IMAGEOUT, '>', "out.pbm" or die "Can't write 'out.pbm': $!";
print IMAGEOUT $cropped_image;
close IMAGEOUT;

There's a limit to the length of a string the substitution functions will handle, which necessitates a bothersome while loop for taller images. Even so, finally under 300!

comperendinous

Posted 2014-08-05T22:40:53.653

Reputation: 466

0

C++/Qt 146

This might technically break rule #2 as Qt is not part of standard C++.

The first argument will be the input filename and the second will be the output filename.

#include<QtGui>
main(int c,char**v){QApplication a(c,v);QPixmap p(v[1]);int w=p.width(),h=p.height(),m=qMin(w,h);p.copy(w-m,h-m,m,m).save(v[2]);}

user30229

Posted 2014-08-05T22:40:53.653

Reputation:

This answer is not valid due to the use of a non standard library, but thanks for taking part :) – rdans – 2014-08-06T19:25:35.540

0

Io, 103 bytes (excluding file path)

i := Image open("img.png");e := (i width) min(i height);i crop(((i width)-e)/2,((i height)-e)/2,e,e) save("crop.png")

You can use Io for CG!

georgeunix

Posted 2014-08-05T22:40:53.653

Reputation: 3 169

This can be heavily 'golfed' even further – georgeunix – 2015-08-27T06:01:32.380

0

Matlab, 116 (130 with file names)

Pretty straightforward.

I=imread('a.jpg');[m,n]=size(I);z=min(m,n);c=m<n;r=[1,ceil((m-z)/2)]*~c+c*[ceil((n-z)/2),1];imwrite(imcrop(I,[r,z-1,z-1]),'z.jpg')

flawr

Posted 2014-08-05T22:40:53.653

Reputation: 40 560