5
\$\begingroup\$

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
\$\endgroup\$
8
  • \$\begingroup\$ 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. \$\endgroup\$
    – beary605
    Commented Feb 9, 2013 at 0:40
  • \$\begingroup\$ @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. \$\endgroup\$
    – user7467
    Commented Feb 9, 2013 at 9:04
  • \$\begingroup\$ 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! \$\endgroup\$
    – Mr Lister
    Commented Feb 10, 2013 at 15:06
  • \$\begingroup\$ @MrLister Thanks you are right. I need to fix that. Is the new version better? \$\endgroup\$
    – user7467
    Commented Feb 10, 2013 at 16:35
  • 1
    \$\begingroup\$ in bash, I just type gimp \$\endgroup\$
    – boothby
    Commented Feb 12, 2013 at 7:11

6 Answers 6

2
\$\begingroup\$

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)
\$\endgroup\$
8
  • \$\begingroup\$ 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. \$\endgroup\$
    – user7467
    Commented Feb 10, 2013 at 22:38
  • \$\begingroup\$ It works in MATLAB, but apparently not in Octave. My bad for not testing it. I'll fix it ASAP. \$\endgroup\$
    – kaoD
    Commented Feb 10, 2013 at 22:59
  • \$\begingroup\$ @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? \$\endgroup\$
    – kaoD
    Commented Feb 11, 2013 at 0:24
  • \$\begingroup\$ @koaD I got it to work in the end. You might have to add "pkg load image" strictly speaking. \$\endgroup\$
    – user7467
    Commented Feb 11, 2013 at 21:44
  • \$\begingroup\$ @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. \$\endgroup\$
    – user7467
    Commented Feb 12, 2013 at 8:54
2
\$\begingroup\$

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);
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ Would you mind putting a nicely formatted version too so we can appreciate the golfing tricks you have done? \$\endgroup\$
    – user7467
    Commented Feb 14, 2013 at 20:53
1
\$\begingroup\$

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").

\$\endgroup\$
3
  • \$\begingroup\$ How are you counting the chars? It seems to be 306 to me using tr -d '[[:blank:]]' < temp.R |wc. \$\endgroup\$
    – user7467
    Commented Feb 12, 2013 at 10:19
  • \$\begingroup\$ 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. \$\endgroup\$
    – plannapus
    Commented Feb 12, 2013 at 10:45
  • \$\begingroup\$ So the actual number is 305 (the line-break after the opening braces and the one before the closing braces being meaningless). \$\endgroup\$
    – plannapus
    Commented Feb 12, 2013 at 10:57
1
\$\begingroup\$

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);
    }
}
\$\endgroup\$
9
  • \$\begingroup\$ 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. \$\endgroup\$
    – user7467
    Commented Feb 12, 2013 at 10:13
  • \$\begingroup\$ @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? \$\endgroup\$
    – Gaffi
    Commented Feb 12, 2013 at 11:48
  • \$\begingroup\$ 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. \$\endgroup\$ Commented Feb 12, 2013 at 12:30
  • \$\begingroup\$ @Gaffi Sure. It's just not what the Octave solution did . I think you are right though. \$\endgroup\$
    – user7467
    Commented Feb 12, 2013 at 14:31
  • 1
    \$\begingroup\$ @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. \$\endgroup\$
    – Joe K
    Commented Feb 13, 2013 at 18:28
1
\$\begingroup\$

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)
\$\endgroup\$
0
1
\$\begingroup\$

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.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.