Cropping an image succintly

4

2

I am constantly frustrated by how complicated it is to write graphical programs that would have been only a few lines of code 20+ years ago. Since the invention of the mouse this has become even worse it seems. I was hoping someone could put me out of my misery and show the shortest code to do the following really basic "modern" operations.

The code will read in an image in any sensible format of your choosing (png, jpg, etc.), display it on the screen, allow you to choose a rectangular region with your mouse and then just save the selected region as an image file.

You are free to use any programming language and any library which hasn't been written especially for this competition. Both should be freely (in both senses) online. The code should be runnable in linux.

To start things off.... here is a very lightly golfed solution in python modified from code to Andrew Cox from https://stackoverflow.com/questions/601874/digital-image-cropping-in-python which takes 1294 1101 characters.

import pygame, sys
from PIL import Image
pygame.init()

def displayImage( s, px, t):
    s.blit(px, px.get_rect())
    if t:
        pygame.draw.rect( s, (128,128,128), pygame.Rect(t[0], t[1], pygame.mouse.get_pos()[0] - t[0], pygame.mouse.get_pos()[1] - t[1]))
    pygame.display.flip()

def setup(path):
    px = pygame.image.load(path)
    s = pygame.display.set_mode( px.get_rect()[2:] )
    s.blit(px, px.get_rect())
    pygame.display.flip()
    return s, px

def mainLoop(s, px):
    t = None
    b = None
    runProgram = True
    while runProgram:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                runProgram = False
            elif event.type == pygame.MOUSEBUTTONUP:
                if not t:
                    t = event.pos
                else:
                    b = event.pos
                    runProgram = False
        displayImage(s, px, t)
    return ( t + b )


s, px = setup(sys.argv[1])
left, upper, right, lower = mainLoop(s, px)
im = Image.open(sys.argv[1])
im = im.crop(( left, upper, right, lower))
im.save(sys.argv[2])

I believe it is traditional to give a time limit, so I will accept the best answer in exactly one week's time.

Current count

  • Octave - 44 + 15 = 59 characters
  • R - 305 characters
  • Python - 343 characters
  • Java - 560 characters

felipa

Posted 2013-02-08T20:57:19.307

Reputation: 895

You should golf your program a little bit more :) Use one letter variables and as little whitespace as possible. Also, you don't need the if __name__ == "__main__": if you're just writing a program. – beary605 – 2013-02-09T00:40:40.067

@beary605 Thanks. I don't want to obfuscate it too much as I was hoping that it might inspire someone to make a great alternative solution. I suspect but don't know that the key may be to find a language+library pair that does almost the work for you. – felipa – 2013-02-09T09:04:56.610

I don't like "You are free to use any (..) libraries you like as long as they are available freely (..) online." This means I can write a library that does all the work, make it available online, and then publish a one-line program in an answer here that does nothing but call the library! – Mr Lister – 2013-02-10T15:06:23.970

@MrLister Thanks you are right. I need to fix that. Is the new version better? – felipa – 2013-02-10T16:35:00.507

@felipa good enough. – Mr Lister – 2013-02-10T18:27:28.207

1in bash, I just type gimp – boothby – 2013-02-12T07:11:12.653

@boothby :) I am not sure that is in the spirit of the game. – felipa – 2013-02-12T07:56:31.633

@felipa Octave should be 44+15! – kaoD – 2013-02-12T21:38:14.280

Answers

3

Octave - 59 chars

pkg load image;imwrite(imcrop(imread(input(''))),input(''))

MATLAB - 44 chars (not free, out of the contest)

imwrite(imcrop(imread(input(''))),input(''))

MATLAB seemed spot-on for this. Requires the Image Processing Toolbox (or package image 2.0.0 from Octave Forge). Usage:

  1. Write at prompt 'input-filename' (single quotes included)
  2. MATLAB: Drag from top-left to bottom-right corner and double click the rectangle. Octave: Click top-left and bottom-right corner
  3. Write 'output-filename' (between single quotes too)

kaoD

Posted 2013-02-08T20:57:19.307

Reputation: 584

This code doesn't work for me in octave. I am not sure which mouse buttons I am meant to select with but I always get "subscript indices must be either positive integers or logicals" in any case. – felipa – 2013-02-10T22:38:56.653

It works in MATLAB, but apparently not in Octave. My bad for not testing it. I'll fix it ASAP. – kaoD – 2013-02-10T22:59:09.657

@felipa I fixed it and shaved some chars. Works fine with Octave + package image 2.0.0. Am I allowed to assume in/out filenames are in a var instead of the ugly input('')s? – kaoD – 2013-02-11T00:24:43.160

@koaD I got it to work in the end. You might have to add "pkg load image" strictly speaking. – felipa – 2013-02-11T21:44:07.643

@koaD You can assume the filenames are given on the command line but the code you give should be self contained and runnable as is. – felipa – 2013-02-12T08:54:44.737

@felipa in MATLAB package imports are not needed, but since the rules require free software I guess I have to stick to Octave. Edited. (btw, I'm kaoD, not koaD! :P) – kaoD – 2013-02-12T21:34:06.900

Are you supposed to count blank spaces? I am not clear on the golfing rules. tr -d '[[:blank:]]' which is used below removes them I think. – felipa – 2013-02-12T21:47:33.703

@felipa as far as I know, spaces are counted usually (I did count spaces). You make the rules tho, so it's up to you. – kaoD – 2013-02-13T00:18:05.320

3

Java (580 560 559)

import java.awt.*;import javax.imageio.*;public class R extends
Frame{static java.awt.image.BufferedImage i;int
x,y,w,h;{setSize(i.getWidth(),i.getHeight());setUndecorated(0<1);show();}public static void
main(String[]a)throws Exception{i=ImageIO.read(System.in);new R();}public boolean handleEvent(Event
e){if(e.id==501){x=e.x;y=e.y;}if(e.id==502){w=e.x-x;if(w<0)x-=w=-w;h=e.y-y;if(h<0)y-=h=-h;try{ImageIO.write(i.getSubimage(x,y,w,h),"png",System.out);System.exit(0);}catch(Exception
E){}}return 0<1;}
public void paint(Graphics g){g.drawImage(i,0,0,this);}}

Takes input image from stdin and writes cropped image to stdout. You can drag in whichever direction you choose and it will automatically flip the coordinates as required. Input in GIF, PNG, JPEG, TIFF, probably one or two others; output in PNG.

With whitespace and a couple of comments:

import java.awt.*;
import javax.imageio.*;

// Extending Frame is the simplest and ugliest way of showing a GUI.
public class R extends Frame {

    // static so that we can read it in main() and save a try/catch block
    static java.awt.image.BufferedImage i;
    int x,y,w,h;

    // Instance initialiser ~= constructor without the signature
    {
        setSize(i.getWidth(),i.getHeight());
        // Don't show frames. This saves handling inset dimensions everywhere
        setUndecorated(0<1);
        show();
    }

    public static void main(String[]a) throws Exception
    {
        i=ImageIO.read(System.in);
        new R();
    }

    // This is deprecated, but it's the shortest way of handling events.
    // I learnt this trick from the Java4k Game Competition
    public boolean handleEvent(Event e) {
        if (e.id == 501) { // MOUSE_DOWN
            x=e.x;
            y=e.y;
        }
        if (e.id == 502) { // MOUSE_UP
            w = e.x - x;
            // If the second click was to the left of the first one, flip them.
            if (w < 0) x -= w = -w;
            h = e.y - y;
            // Similarly.
            if (h < 0) y -= h = -h;

            try {
                ImageIO.write(i.getSubimage(x,y,w,h), "png", System.out);
                System.exit(0);
            } catch(Exception E){
            }
        }
        return 0<1;
    }

    public void paint(Graphics g) {
        g.drawImage(i,0,0,this);
    }
}

Peter Taylor

Posted 2013-02-08T20:57:19.307

Reputation: 41 901

Would you mind putting a nicely formatted version too so we can appreciate the golfing tricks you have done? – felipa – 2013-02-14T20:53:14.480

2

R - 325 305 characters

f=scan(,"");l=locator;r=round;i=as.raster(png::readPNG(f));n=nrow(i);m=ncol(i);x11(h=7,w=7*m/n);p=function(X){par(mar=rep(0,4));m=ncol(X);n=nrow(X);plot(c(1,m),c(1,n),xaxs="i",yaxs="i");rasterImage(X,1,1,m,n)};p(i);a=l(1);b=l(1);j=i[r(n-a$y):r(n-b$y),r(a$x):r(b$x)];png(w=ncol(j),h=nrow(j));p(j);dev.off()

Or fully developed:

f=scan(,"")
l=locator
r=round
i=as.raster(png::readPNG(f))
n=nrow(i)
m=ncol(i)
x11(h=7,w=7*m/n)
p=function(X){
par(mar=rep(0,4))
m=ncol(X)
n=nrow(X)
plot(c(1,m),c(1,n),xaxs="i",yaxs="i")
rasterImage(X,1,1,m,n)
}
p(i)
a=l(1)
b=l(1)
j=i[r(n-a$y):r(n-b$y),r(a$x):r(b$x)]
png(w=ncol(j),h=nrow(j))
p(j)
dev.off()

Tested on a Mac, untested on Linux but should work.
Package png needs to be installed.
At prompt, should be provided with path to png file to crop. Then when the picture appears, click twice: once for the left upper corner of selection rectangle and a second time for the right lower corner.
Create a cropped png file in working directory with default name ("Rplot001.png").

plannapus

Posted 2013-02-08T20:57:19.307

Reputation: 8 610

How are you counting the chars? It seems to be 306 to me using tr -d '[[:blank:]]' < temp.R |wc. – felipa – 2013-02-12T10:19:02.930

So I'm guessing you're counting the line-break then. It's my first week code-golfing I might have overlook that. The issue is that not all line-break are meaningful in R so I might need to edit accordingly. – plannapus – 2013-02-12T10:45:13.933

So the actual number is 305 (the line-break after the opening braces and the one before the closing braces being meaningless). – plannapus – 2013-02-12T10:57:46.050

2

Java (Applet), 706 698 590

By no means is Java ever considered terse, and it certainly doesn't have the most elegant handling of image display or mouse interaction, but this really isn't that bad. I'm wondering if a solution using Swing or some other library might be shorter.

import java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.imageio.*;


public class R extends java.applet.Applet{
    Scanner s=new java.util.Scanner(System.in);
    java.awt.image.BufferedImage i;
    int x,y;
    {
        try{
            i=ImageIO.read(new File(s.nextLine()));
        }catch(Exception e){}
        addMouseListener(new MouseAdapter(){
            public void mousePressed(MouseEvent e){
                x=e.getX();y=e.getY();
            }
            public void mouseReleased(MouseEvent e){
                try{
                    ImageIO.write(i.getSubimage(x,y,e.getX()-x,e.getY()-y),"png",new File(s.nextLine()));
                }catch(Exception z){}
            }
        });
    }
    public void paint(java.awt.Graphics g){
        g.drawImage(i,0,0,this);
    }
}

Joe K

Posted 2013-02-08T20:57:19.307

Reputation: 1 065

I see you are counting characters using tr -d '[[:blank:]]' < file.java |wc . If you take the view that you don't need to include the imports you are down to 590 chars. – felipa – 2013-02-12T10:13:30.223

@felipa If part of the solution is to match the best code with the best library, then perhaps counting the include is a good idea? – Gaffi – 2013-02-12T11:48:30.897

You can almost certainly golf this more by not using listeners. Just override an event handling method of Applet (handleEvent or processEvent). I think you'll also find that BufferedImage.getSubimage is shorter than copying the data manually. – Peter Taylor – 2013-02-12T12:30:42.480

@Gaffi Sure. It's just not what the Octave solution did . I think you are right though. – felipa – 2013-02-12T14:31:47.977

@felipa I'm counting the minimum number of characters to make a working program. That includes whitespace in some cases, and definitely includes imports. – Joe K – 2013-02-12T22:35:41.753

@PeterTaylor thanks for the tips. getSubimage definitely helps, but I don't think processEvent will. Too much added to ensure that the event is a mouse down or mouse up, and not something like a window resize or a focus or whatever. – Joe K – 2013-02-12T22:36:55.733

Seems to work ok for me... – Peter Taylor – 2013-02-12T22:52:31.413

@JoeK That makes sense. How exactly are you doing the counting? By hand? – felipa – 2013-02-13T10:22:37.497

1@felipa Yep, pretty much deleting every newline and tab character and then just putting the cursor at the end of the program and looking at the column number. – Joe K – 2013-02-13T18:28:49.717

2

Python/PyGame (343C)

Usage:

python this.py image.png result.png

which read from image.png and write to result.png.

import pygame as P
import sys
D=P.display
I=P.image
D.init()
f,g=sys.argv[1:3]
m=I.load(f)
s=D.set_mode(m.get_size())
r=k=0
c=1
while c:
 for e in P.event.get():
  t=e.type
  if t==5:x,y=e.pos;k=1
  if t==4 and k:i,j=e.pos;r=(x,y,i-x,j-y)
  if t==6:c=0
 s.blit(m,(0,0))
 if r and c:P.draw.rect(s,0,r,1)
 D.flip()
q=s.subsurface(r)
I.save(q,g)

Ray

Posted 2013-02-08T20:57:19.307

Reputation: 1 946

1

I know the time limit is up, but here's how to do it in 34 characters using Bash:

display $1&K=$!;import $2;kill $K

Call it like this: 'script.sh input.png output.png'.

This uses the ImageMagick tools/library to first display the image. Then it lets you select an area to take a screenshot of using the mouse. This screenshot is effectively a crop of the image. Finally, the display is closed.

Wander Nauta

Posted 2013-02-08T20:57:19.307

Reputation: 3 039