#!/usr/bin/perl
#
# imgdim.pl
#
# For Windows 9x/2000/XP, Linux, BSD, & Mac OS X.
#
# Extracts an image's dimensions and formats them for use
# in an (X)HTML tag, like so:
#
# width="132" height="237"
#
# In Windows, the formatted dimensions are put on the
# clipboard. Under Linux, the formatted dimensions are
# written to standard out.
#
# This program works with the standard Web images types
# GIF, JPEG, and PNG. If the file name doesn't end in GIF or
# PNG, it is assumed to be a JPEG. (This is a workaround for
# DOS's 8+3 file name mangling,)
#
# Windows use: this program is invoked by a batch file that
# resides on the desktop. Dragging a file to the batch file's
# icon will invoke this script on the dropped file.
#
# Linux use: run from the command line as
# "perl imgdim.pl ". Depending on your environment,
# you may also be able to run it as "imgdim.pl ".
#
# Mac OS X use: put the script in your home directory. Create
# a new script in the Script Editor:
#
# on open file_
# set file_ to quoted form of POSIX path of file_
# set the clipboard to (do shell script "" & file_)
# end open
#
# Save as an application.
#
# If the dropped file isn't one of the recognized image
# types, or isn't openable, this program will exit without
# an error. (It's better to fail silently than to destroy
# the clipboard's current contents.)
#
# Mark L. Irons
# 4-5 April 2002; revised 20 July 2002
#
# Adrian Tymes
# 18 July 2002
#
# Scott Crevier
# 29 April 2004
#
# Barak Shilo
# 30 October 2006
#
#
# CHANGE HISTORY
#
# -- 30 October 2006 --
# Incorporated Barak's changes for Max OS X. Changed format string
# for GIFs from "SS" to "vv" to deal with endian problem on PowerPCs.
#
# -- 29 April 2004 --
# Incorporated Scott's improvements to OS detection.
#
# -- 23 September 2003 --
# Changed output to XHTML.
#
# -- 20 July 2002 --
# Modified the information at top to incorporate Adrian's changes.
#
# -- 18 July 2002 --
# Tweaked to make it run on Linux too.
#
# -- 5 April 2002 --
# Added JPEG & PNG support. Not sure JPEG logic is completely
# correct; is it legal for the opening marker (0xFFD8) to
# precede later frames?
#
# -- 4 April 2002 --
# Program created, and GIF implemented.
#
#
# NOTES
#
# 1. unpack() is used extensively. The format strings may not
# port to all systems, though they do seem to work on Windows,
# Linux, and BSD.
#
# 2. Tested successfully on:
# * FreeBSD 4.8-STABLE (perl 5.8.3)
# * Red Hat Linux 6.2
# * SuSE Linux 8.1 (perl 5.8.0)
# * Windows 95 (ActivePerl 5.6.0)
# * Windows 2000 Professional (ActivePerl 5.8.0)
# * Windows XP 2002 Professional (ActivePerl 5.8.2)
# * Mac OS X
#
#
# KNOWN BUGS
#
# 1. If the script is invoked on a file that isn't one of the recognized
# image types, garbage will be returned.
#
#
# LEGAL STUFF
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License as
# published by the Free Software Foundation; either version 2 of
# the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You may find the full GNU General Public License at
# http://www.gnu.org/licenses/gpl.txt, or write to the Free
# Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
# MA 02111-1307 USA.
#--------------------------------------------------------------------
#
# P L A T F O R M
#
#--------------------------------------------------------------------
# Figure out what platform we're on (windows or unix). Different
# os's use different environment variables, so this line of code is
# not fool proof in all environments. Just hard-code it if necessary.
$platform = ($ENV{'SHELL'} =~ m|^/|) ? 'unix' : 'windows';
#--------------------------------------------------------------------
#
# P A T T E R N S
#
#--------------------------------------------------------------------
# Patterns used to match filenames based on their extensions
$GIFpattern = ".*\.(g|G)(i|I)(f|F)\$";
$PNGpattern = ".*\.(p|P)(n|N)(g|G)\$";
# JPEGs can have multiple patterns, like ".jpeg" or ".jpg" or
# ".jpe", so default to JPEG if no other pattern matches. If it's
# not a JPEG but it doesn't match the other known patterns, we'll
# error out anyway.
#--------------------------------------------------------------------
#
# F I L E C H E C K
#
#--------------------------------------------------------------------
# $ARGV[0] holds the name of the dragged-and-dropped file.
# We copy it into $_ so that pattern match tests look nice.
$_ = $ARGV[0];
# make sure the file exists
if (! -r) {
# print STDOUT "File not found: $_\n"; # uncomment if you want
# an error message if the
# file isn't found
exit(1);
}
#--------------------------------------------------------------------
#
# C O D E
#
#--------------------------------------------------------------------
# Now we'll try to figure out what kind of file it is, and
# extract the dimensions if it's one we're interested in.
# If there is any kind of error, (-1,-1) will be returned.
# Otherwise, a legitimate image size will be returned.
my ($w,$h) = (-1,-1);
if (/$GIFpattern/) {
($w,$h) = &extractGIFdimensions;
}
elsif (/$PNGpattern/) {
($w,$h) = &extractPNGdimensions;
}
else {
($w,$h) = &extractJPEGdimensions;
}
$output = "width=\"$w\" height=\"$h\"";
# If the width and height are valid, then:
# For Linux: print them to standard out.
# For Windows: create a clipboard object and paste them.
if (($width >= 0) && ($height >= 0)) {
if ($platform eq 'unix') {
print STDOUT "$output\n";
} else {
require Win32::Clipboard;
$CLIP = Win32::Clipboard();
$CLIP->Set("$output");
}
} else {
if ($platform eq 'unix') {
print STDOUT "Could not determine dimensions of '" .
$ARGV[0] . "'\n";
} else {
# fail silently, do not replace clipboard's contents
}
}
#----------------------------------------------------------------
# sub extractGIFdimensions
#
# In GIF files, the dimensions are four bytes starting at byte 6:
#
# offset 6-7 width lsb,msb order
# 8-9 height lsb,msb order
#----------------------------------------------------------------
sub extractGIFdimensions {
open(IMAGEFILE,$ARGV[0]) || return(-1,-1);
binmode IMAGEFILE;
seek(IMAGEFILE,6,0);
if (read(IMAGEFILE,$dimensions,4) != 4) {
close IMAGEFILE;
return(-1,-1);
}
close IMAGEFILE;
($width,$height) = unpack("vv",$dimensions);
return ($width,$height);
}
#----------------------------------------------------------------
# sub extractJPEGdimensions
#
#
# JPEG is a more complicated format than GIF. It starts with
# two bytes (0xFFD8) identifying a file as a JPEG, followed by
# frames of data. Frames have the following structure:
#
# offset 0 0xFF frame begin
# 1 marker frame type
# 2-3 length length of frame
# 4 precision ?
# 5-6 height msb,lsb order
# 7-8 width msb,lsb order
# 9+ data
#
# The frame types we're interested in is any of the following:
#
# 0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7, 0xC9,
# 0xCA, 0xCB, 0xCD, 0xCE, 0xCF
#
# So we do the following:
#
# 1. Read the first two bytes.
# 2. If they're not 0xFFD8, exit. It's not a JPEG.
# 3. Read the next two bytes.
# 4. If the marker isn't one of the interesting types,
# skip ahead to the next frame and goto step 3.
# 5. Read & return the height and width.
#
#----------------------------------------------------------------
sub extractJPEGdimensions {
@InterestingMarkers = (0xC0, 0xC1, 0xC2, 0xC3, 0xC5, 0xC6, 0xC7,
0xC9, 0xCA, 0xCB, 0xCD, 0xCE, 0xCF);
open(IMAGEFILE,$ARGV[0]) || return(-1,-1);
binmode IMAGEFILE;
#
# Read the first marker.
#
if ( (read(IMAGEFILE,$firstmarker,2) != 2) ||
(unpack("n",$firstmarker) != 0xFFD8)) {
close(IMAGEFILE);
return(-1,-1);
}
#
# Follow the chain of frames until we find one with
# the image dimensions.
#
while (1) {
#
# Read the header of the next frame
#
if (read(IMAGEFILE,$frame_header,2) != 2) {
close(IMAGEFILE);
return(-1,-1);
}
($boundary,$marker) = unpack("CC",$frame_header);
if ($boundary != 0xFF) {
close(IMAGEFILE);
return(-1,-1);
}
#
# Does this marker indicate a frame with dimension information?
# Exit the loop if it is.
#
$found = 1;
foreach (@InterestingMarkers) {
if ($marker == $_) {
$found = 0;
last;
}
}
last if ($found == 0);
#
# This isn't a frame we want, so read the length and
# skip ahead.
#
if (read(IMAGEFILE,$frame_length,2) != 2) {
close(IMAGEFILE);
return(-1,-1);
}
$frame_length = unpack("n",$frame_length);
seek(IMAGEFILE,$frame_length-2,1);
}
#
# This frame should hold the image's dimensions. However,
# two pieces of information precede the image dimensions:
# a 2-byte frame length, and a 1-byte precision (whatever
# that is). Read and discard them.
#
if (read(IMAGEFILE,$trash,3) != 3) {
close(IMAGEFILE);
return(-1,-1);
}
#
# Read the dimensions, unpack them, and put them on the
# Windows clipboard.
#
if ((read(IMAGEFILE,$height,2) != 2) || (read(IMAGEFILE,$width,2) != 2)) {
close(IMAGEFILE);
return(-1,-1);
}
$height = unpack("n",$height);
$width = unpack("n",$width);
#
# Clean up.
#
close IMAGEFILE;
return($width,$height);
}
#----------------------------------------------------------------
# sub extractPNGdimensions
#
# In PNG files, the dimensions are eight bytes starting at
# offset 16:
#
# offset 16-19 width 4 bytes, msb to lsb in order
# 20-23 height 4 bytes, msb to lsb in order
#----------------------------------------------------------------
sub extractPNGdimensions {
open(IMAGEFILE,$ARGV[0]) || return(-1,-1);
binmode IMAGEFILE;
#
# We might consider checking whether the file is a PNG.
# It should start with the 8-byte pattern
# (137, 80, 78, 71, 13, 10, 26, 10).
#
# For now we'll just assume it is and grab the
# dimensions.
#
seek(IMAGEFILE,16,0);
if (read(IMAGEFILE,$dimensions,8) != 8) {
close IMAGEFILE;
return(-1,-1);
}
close IMAGEFILE;
($width,$height) = unpack("NN",$dimensions);
return($width,$height);
}