diff options
| author | Pepper <pepper@scannerjammer.com> | 2015-02-26 02:03:24 -0500 |
|---|---|---|
| committer | Pepper <pepper@scannerjammer.com> | 2015-02-26 02:03:24 -0500 |
| commit | d489eb062653a2726d4fbd42bfd34835dc770d4b (patch) | |
| tree | b6f73ee095d713feb7b656e9d178126e83a62f63 | |
first
| -rw-r--r-- | .gitignore | 2 | ||||
| -rwxr-xr-x | 3Drotate | 873 | ||||
| -rwxr-xr-x | bevelborder | 295 | ||||
| -rwxr-xr-x | breaker.py | 394 | ||||
| -rwxr-xr-x | db.py | 31 | ||||
| -rwxr-xr-x | gradient.py | 257 | ||||
| -rwxr-xr-x | grid | 241 | ||||
| -rw-r--r-- | imbreak_main.js | 113 | ||||
| -rw-r--r-- | imgradient_index.html | 532 | ||||
| -rwxr-xr-x | imgrid.py | 356 | ||||
| -rw-r--r-- | imgrid_main.js | 121 | ||||
| -rwxr-xr-x | pbserver.py | 182 | ||||
| -rw-r--r-- | s3.py | 618 | ||||
| -rw-r--r-- | s3config.py | 3 | ||||
| -rw-r--r-- | test.sh | 6 |
15 files changed, 4024 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f739444 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +./s3config.py +./db.py diff --git a/3Drotate b/3Drotate new file mode 100755 index 0000000..227b92d --- /dev/null +++ b/3Drotate @@ -0,0 +1,873 @@ +#!/bin/bash +# +# Developed by Fred Weinhaus 8/18/2007 .......... revised 11/26/2011 +# +# USAGE: 3Drotate option=value infile outfile +# USAGE: 3Drotate [-h or -help] +# +# OPTIONS: any one or more +# +# pan value rotation about image vertical centerline; +# -180 to +180 (deg); default=0 +# tilt value rotation about image horizontal centerline; +# -180 to +180 (deg); default=0 +# roll value rotation about the image center; +# -180 to +180 (deg); default=0 +# pef value perspective exaggeration factor; +# 0 to 3.19; default=1 +# idx value +/- pixel displacement in rotation point right/left +# in input from center; default=0 +# idy value +/- pixel displacement in rotation point down/up +# in input from center; default=0 +# odx value +/- pixel displacement in rotation point right/left +# in output from center; default=0 +# ody value +/- pixel displacement in rotation point down/up +# in output from center; default=0 +# zoom value output zoom factor; where value > 1 means zoom in +# and < -1 means zoom out; value=1 means no change +# bgcolor value the background color value; any valid IM image +# color specification (see -fill); default is black +# skycolor value the sky color value; any valid IM image +# color specification (see -fill); default is black +# auto c center bounding box in output +# (odx and ody ignored) +# auto zc zoom to fill and center bounding box in output +# (odx, ody and zoom ignored) +# auto out creates an output image of size needed to hold +# the transformed image; (odx, ody and zoom ignored) +# vp value virtual-pixel method; any valid IM virtual-pixel method; +# default=background +# +### +# +# NAME: 3DROTATE +# +# PURPOSE: To apply a perspective distortion to an image by providing rotation angles, +# zoom, offsets, background color, perspective exaggeration and auto zoom/centering. +# +# DESCRIPTION: 3DROTATE applies a perspective distortion to an image +# by providing any combination of three optional rotation angle: +# pan, tilt and roll with optional offsets and zoom and with an optional +# control of the perspective exaggeration. The image is treated as if it +# were painted on the Z=0 ground plane. The picture plane is then rotated +# and then perspectively projected to a camera located a distance equal to +# the focal length above the ground plane looking straight down along +# the -Z direction. +# +# +# ARGUMENTS: +# +# PAN is a rotation of the image about its vertical +# centerline -180 to +180 degrees. Positive rotations turn the +# right side of the image away from the viewer and the left side +# towards the viewer. Zero is no rotation. A PAN of +/- 180 deg +# achieves the same results as -flip. +# +# TILT is a rotation of the image about its horizontal +# centerline -180 to +180 degrees. Positive rotations turn the top +# of the image away from the viewer and the bottom towards the +# viewer. Zero is no rotation. A TILT of +/- 180 deg +# achieves the same results as -flop. +# +# ROLL (like image rotation) is a rotation in the plane of the +# the image -180 to +180 degrees. Positive values are clockwise +# and negative values are counter-clockwise. Zero is no rotation. +# A ROLL of any angle achieves the same results as -rotate. +# +# PAN, TILT and ROLL are order dependent. If all three are provided, +# then they will be done in whatever order specified. +# +# PEF is the perspective exaggeration factor. It ranges from 0 to 3.19. +# A normal perspective is achieved with the default of 1. As PEF is +# increased from 1, the perspective effect moves towards that of +# a wide angle lens (more distortion). If PEF is decreased from 1 +# the perspective effect moves towards a telephoto lens (less +# distortion). PEF of 0.5 achieves an effect close to no perspective +# distortion. As pef gets gets larger than some value which depends +# upon the larger the pan, tilt and roll angles become, one reaches +# a point where some parts of the picture become so distorted that +# they wrap around and appear above the "horizon" +# +# IDX is the a pixel displacement of the rotation point in the input image +# from the image center. Positive values shift to the right along the +# sample direction; negative values shift to the left. The default=0 +# corresponds to the image center. +# +# IDY is the a pixel displacement of the rotation point in the input image +# from the image center. Positive values shift to downward along the +# line direction; negative values shift upward. The default=0 +# corresponds to the image center. +# +# ODX is the a pixel displacement from the center of the output image where +# one wants the corresponding input image rotation point to appear. +# Positive values shift to the right along the sample direction; negative +# values shift to the left. The default=0 corresponds to the output image center. +# +# ODY is the a pixel displacement from the center of the output image where +# one wants the corresponding input image rotation point to appear. +# Positive values shift downward along the sample direction; negative +# values shift upward. The default=0 corresponds to the output image center. +# +# ZOOM is the output image zoom factor. Values > 1 (zoomin) cause the image +# to appear closer; whereas values < 1 (zoomout) cause the image to +# appear further away. +# +# BGCOLOR is the color of the background to use to fill where the output image +# is outside the area of the perspective of the input image. See the IM function +# -fill for color specifications. Note that when using rgb(r,g,b), this must be +# enclosed in quotes after the equal sign. +# +# SKYCOLOR is the color to use in the 'sky' area above the perspective 'horizon'. +# See the IM function -fill for color specifications. Note that when using +# rgb(r,g,b), this must be enclosed in quotes after the equal sign. +# +# AUTO can be either c, zc or out. If auto is c, then the resulting perspective +# of the input image will have its bounding box centered in the output image +# whose size will be the same as the input image. If +# auto is zc, then the resulting perspective of the input image will have its +# bounding box zoomed to fill its largest dimension to match the size of the +# the input image and the other dimension will be centered in the output. If +# auto is out, then the output image will be made as large or as small as +# needed to just fill out the transformed input image. If any of these are +# present, then the arguments OSHIFTX, OSHIFTY are ignored. +# +# VP is the virtual-pixel method, which allows the image to be extended outside +# its bounds. For example, vp=background, then the background color is used to +# fill the area in the output image which is outside the perspective view of +# the input image. If vp=tile, then the perspective view will be tiled to fill +# the output image. +# +# NOTE: The output image size will be the same as the input image size due +# to current limitations on -distort Perspective. +# +# CAVEAT: No guarantee that this script will work on all platforms, +# nor that trapping of inconsistent parameters is complete and +# foolproof. Use At Your Own Risk. +# +###### +# + +# set default value +# rotation angles and rotation matrix +pan=0 +tilt=0 +roll=0 +R0=(1 0 0) +R1=(0 1 0) +R2=(0 0 1) + +# scaling output only +sx=1 +sy=1 + +# offset du,dv = output; relative to center of image +du=0 +dv=0 + +# offset di,dj = input; relative to center of image +di=0 +dj=0 + +# perspective exaggeration factor +pef=1 + +# zoom +zoom=1 + +# background color +bgcolor="black" + +# sky color +skycolor="black" + +# virtual-pixel method +vp="background" + +# set directory for temporary files +dir="." # suggestions are dir="." or dir="/tmp" + +# compute pi +pi=`echo "scale=10; 4*a(1)" | bc -l` + + +# set up functions to report Usage and Usage with Description +PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path +PROGDIR=`dirname $PROGNAME` # extract directory of program +PROGNAME=`basename $PROGNAME` # base name of program +usage1() + { + echo >&2 "" + echo >&2 "$PROGNAME:" "$@" + sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" + } +usage2() + { + echo >&2 "" + echo >&2 "$PROGNAME:" "$@" + sed >&2 -n '/^######/q; /^#/!q; s/^#*//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" + } + +# function to report error messages, usage and exit +errMsg() + { + echo "" + echo $1 + echo "" + usage1 + exit 1 + } + +# function to do dot product of 2 three element vectors +function DP3 + { + V0=($1) + V1=($2) + DP=`echo "scale=10; (${V0[0]} * ${V1[0]}) + (${V0[1]} * ${V1[1]}) + (${V0[2]} * ${V1[2]})" | bc` + } + +# function to do 3x3 matrix multiply M x N where input are rows of each matrix; M1 M2 M3 N1 N2 N3 +function MM3 + { + [ $# -ne 6 ] && errMsg "--- NOT A VALID SET OF MATRIX PARAMETERS ---" + M0=($1) + M1=($2) + M2=($3) + N0=($4) + N1=($5) + N2=($6) + [ ${#M0[*]} -ne 3 -a ${#M1[*]} -ne 3 -a ${#M2[*]} -ne 3 -a ${#N0[*]} -ne 3 -a ${#N1[*]} -ne 3 -a ${#N2[*]} -ne 3 ] && errMsg "--- NOT A VALID SET OF MATRIX ROWS ---" + # extract columns n from rows N + n0=(${N0[0]} ${N1[0]} ${N2[0]}) + n1=(${N0[1]} ${N1[1]} ${N2[1]}) + n2=(${N0[2]} ${N1[2]} ${N2[2]}) + DP3 "${M0[*]}" "${n0[*]}" + P00=$DP + DP3 "${M0[*]}" "${n1[*]}" + P01=$DP + DP3 "${M0[*]}" "${n2[*]}" + P02=$DP + DP3 "${M1[*]}" "${n0[*]}" + P10=$DP + DP3 "${M1[*]}" "${n1[*]}" + P11=$DP + DP3 "${M1[*]}" "${n2[*]}" + P12=$DP + DP3 "${M2[*]}" "${n0[*]}" + P20=$DP + DP3 "${M2[*]}" "${n1[*]}" + P21=$DP + DP3 "${M2[*]}" "${n2[*]}" + P22=$DP + P0=($P00 $P01 $P02) + P1=($P10 $P11 $P12) + P2=($P20 $P21 $P22) + } + +# function to project points from input to output domain +function forwardProject + { + ii=$1 + jj=$2 + numu=`echo "scale=10; ($P00 * $ii) + ($P01 * $jj) + $P02" | bc` + numv=`echo "scale=10; ($P10 * $ii) + ($P11 * $jj) + $P12" | bc` + den=`echo "scale=10; ($P20 * $ii) + ($P21 * $jj) + $P22" | bc` + uu=`echo "scale=0; $numu / $den" | bc` + vv=`echo "scale=0; $numv / $den" | bc` + } + +# function to project points from input to output domain +function inverseProject + { + uu=$1 + vv=$2 + numi=`echo "scale=10; ($Q00 * $uu) + ($Q01 * $vv) + $Q02" | bc` + numj=`echo "scale=10; ($Q10 * $uu) + ($Q11 * $vv) + $Q12" | bc` + den=`echo "scale=10; ($Q20 * $uu) + ($Q21 * $vv) + $Q22" | bc` + ii=`echo "scale=0; $numi / $den" | bc` + jj=`echo "scale=0; $numj / $den" | bc` + } + +# function to invert a 3 x 3 matrix using method of adjoint +# inverse is the transpose of the matrix of cofactors divided by the determinant +function M3inverse + { + m00=$1 + m01=$2 + m02=$3 + m10=$4 + m11=$5 + m12=$6 + m20=$7 + m21=$8 + m22=$9 + c00=`echo "scale=10; ($m11 * $m22) - ($m21 * $m12)" | bc` + c01=`echo "scale=10; ($m20 * $m12) - ($m10 * $m22)" | bc` + c02=`echo "scale=10; ($m10 * $m21) - ($m20 * $m11)" | bc` + c10=`echo "scale=10; ($m21 * $m02) - ($m01 * $m22)" | bc` + c11=`echo "scale=10; ($m00 * $m22) - ($m20 * $m02)" | bc` + c12=`echo "scale=10; ($m20 * $m01) - ($m00 * $m21)" | bc` + c20=`echo "scale=10; ($m01 * $m12) - ($m11 * $m02)" | bc` + c21=`echo "scale=10; ($m10 * $m02) - ($m00 * $m12)" | bc` + c22=`echo "scale=10; ($m00 * $m11) - ($m10 * $m01)" | bc` + det=`echo "scale=10; ($m00 * $c00) + ($m01 * $c01) + ($m02 * $c02)" | bc` + idet=`echo "scale=10; 1 / $det" | bc` + Q00=`echo "scale=10; $c00 * $idet" | bc` + Q01=`echo "scale=10; $c10 * $idet" | bc` + Q02=`echo "scale=10; $c20 * $idet" | bc` + Q10=`echo "scale=10; $c01 * $idet" | bc` + Q11=`echo "scale=10; $c11 * $idet" | bc` + Q12=`echo "scale=10; $c21 * $idet" | bc` + Q20=`echo "scale=10; $c02 * $idet" | bc` + Q21=`echo "scale=10; $c12 * $idet" | bc` + Q22=`echo "scale=10; $c22 * $idet" | bc` + Q0=($Q00 $Q01 $Q02) + Q1=($Q10 $Q11 $Q12) + Q2=($Q20 $Q21 $Q22) + } + +# function to test if entry is floating point number +function testFloat + { + test1=`expr "$1" : '^[0-9][0-9]*$'` # counts same as above but preceeded by plus or minus + test2=`expr "$1" : '^[+-][0-9][0-9]*$'` # counts one or more digits + test3=`expr "$1" : '^[0-9]*[\.][0-9]*$'` # counts 0 or more digits followed by period followed by 0 or more digits + test4=`expr "$1" : '^[+-][0-9]*[\.][0-9]*$'` # counts same as above but preceeded by plus or minus + floatresult=`expr $test1 + $test2 + $test3 + $test4` +# [ $floatresult = 0 ] && errMsg "THE ENTRY $1 IS NOT A FLOATING POINT NUMBER" + } + +# get input image size +function imagesize + { + width=`identify -format %w $tmpA` + height=`identify -format %h $tmpA` + } + +# test for correct number of arguments and get values +if [ $# -eq 0 ] + then + # help information + echo "" + usage2 + exit 0 +elif [ $# -gt 15 ] + then + errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---" +else + while [ $# -gt 0 ] + do + # get parameter values + case "$1" in + -h|-help) # help information + echo "" + usage2 + exit 0 + ;; + -) # STDIN and end of arguments + break + ;; + -*) # any other - argument + errMsg "--- UNKNOWN OPTION ---" + ;; + pan[=]*) # pan angle + arg="$1=" + pan=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + pan=`echo "$pan" | sed 's/^[+]\(.*\)$/\1/'` + # pantest>0 if floating point number; otherwise pantest=0 + testFloat "$pan"; pantest=$floatresult + pantestA=`echo "$pan < - 180" | bc` + pantestB=`echo "$pan > 180" | bc` + [ $pantest -eq 0 ] && errMsg "PAN=$pan IS NOT A NUMBER" + [ $pantestA -eq 1 -o $pantestB -eq 1 ] && errMsg "PAN=$pan MUST BE GREATER THAN -180 AND LESS THAN +180" + panang=`echo "scale=10; $pi * $pan / 180" | bc` + sinpan=`echo "scale=10; s($panang)" | bc -l` + sinpanm=`echo "scale=10; - $sinpan" | bc` + cospan=`echo "scale=10; c($panang)" | bc -l` + Rp0=($cospan 0 $sinpan) + Rp1=(0 1 0) + Rp2=($sinpanm 0 $cospan) + # do matrix multiply to get new rotation matrix + MM3 "${Rp0[*]}" "${Rp1[*]}" "${Rp2[*]}" "${R0[*]}" "${R1[*]}" "${R2[*]}" + R0=(${P0[*]}) + R1=(${P1[*]}) + R2=(${P2[*]}) + ;; + tilt[=]*) # tilt angle + arg="$1=" + tilt=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + tilt=`echo "$tilt" | sed 's/^[+]\(.*\)$/\1/'` + # tilttest>0 if floating point number; otherwise tilttest=0 + testFloat "$tilt"; tilttest=$floatresult + tilttestA=`echo "$tilt < - 180" | bc` + tilttestB=`echo "$tilt > 180" | bc` + [ $tilttest -eq 0 ] && errMsg "tilt=$tilt IS NOT A NUMBER" + [ $tilttestA -eq 1 -o $tilttestB -eq 1 ] && errMsg "TILT=$tilt MUST BE GREATER THAN -180 AND LESS THAN +180" + tiltang=`echo "scale=10; $pi * $tilt / 180" | bc` + sintilt=`echo "scale=10; s($tiltang)" | bc -l` + sintiltm=`echo "scale=10; - $sintilt" | bc` + costilt=`echo "scale=10; c($tiltang)" | bc -l` + Rt0=(1 0 0) + Rt1=(0 $costilt $sintilt) + Rt2=(0 $sintiltm $costilt) + # do matrix multiply to get new rotation matrix + MM3 "${Rt0[*]}" "${Rt1[*]}" "${Rt2[*]}" "${R0[*]}" "${R1[*]}" "${R2[*]}" + R0=(${P0[*]}) + R1=(${P1[*]}) + R2=(${P2[*]}) + ;; + roll[=]*) # roll angle + arg="$1=" + roll=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + roll=`echo "$roll" | sed 's/^[+]\(.*\)$/\1/'` + # rolltest>0 if floating point number; otherwise rolltest=0 + testFloat "$roll"; rolltest=$floatresult + rolltestA=`echo "$roll < - 180" | bc` + rolltestB=`echo "$roll > 180" | bc` + [ $rolltest -eq 0 ] && errMsg "roll=$roll IS NOT A NUMBER" + [ $rolltestA -eq 1 -o $rolltestB -eq 1 ] && errMsg "ROLL=$roll MUST BE GREATER THAN -180 AND LESS THAN +180" + rollang=`echo "scale=10; $pi * $roll / 180" | bc` + sinroll=`echo "scale=10; s($rollang)" | bc -l` + sinrollm=`echo "scale=10; - $sinroll" | bc` + cosroll=`echo "scale=10; c($rollang)" | bc -l` + Rr0=($cosroll $sinroll 0) + Rr1=($sinrollm $cosroll 0) + Rr2=(0 0 1) + # do matrix multiply to get new rotation matrix + MM3 "${Rr0[*]}" "${Rr1[*]}" "${Rr2[*]}" "${R0[*]}" "${R1[*]}" "${R2[*]}" + R0=(${P0[*]}) + R1=(${P1[*]}) + R2=(${P2[*]}) + ;; + pef[=]*) # pef + arg="$1=" + pef=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + pef=`echo "$pef" | sed 's/^[+]\(.*\)$/\1/'` + # peftest>0 if floating point number; otherwise peftest=0 + testFloat "$pef"; peftest=$floatresult + peftestA=`echo "$pef < 0" | bc` + peftestB=`echo "$pef > 3.19" | bc` + [ $peftest -eq 0 ] && errMsg "PEF=$pef IS NOT A NUMBER" + ;; + idx[=]*) # input x shift + arg="$1=" + di=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + di=`echo "$di" | sed 's/^[+]\(.*\)$/\1/'` + # ditest>0 if floating point number; otherwise ditest=0 + testFloat "$di"; ditest=$floatresult + [ $ditest -eq 0 ] && errMsg "ISHIFTX=$di IS NOT A NUMBER" + ;; + idy[=]*) # input y shift + arg="$1=" + dj=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + dj=`echo "$dj" | sed 's/^[+]\(.*\)$/\1/'` + # djtest>0 if floating point number; otherwise ditest=0 + testFloat "$dj"; djtest=$floatresult + [ $djtest -eq 0 ] && errMsg "ISHIFTY=$dj IS NOT A NUMBER" + ;; + odx[=]*) # output x shift + arg="$1=" + du=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + du=`echo "$du" | sed 's/^[+]\(.*\)$/\1/'` + # dutest>0 if floating point number; otherwise ditest=0 + testFloat "$du"; dutest=$floatresult + [ $dutest -eq 0 ] && errMsg "OSHIFTX=$du IS NOT A NUMBER" + ;; + ody[=]*) # output y shift + arg="$1=" + dv=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + dv=`echo "$dv" | sed 's/^[+]\(.*\)$/\1/'` + # dvtest>0 if floating point number; otherwise ditest=0 + testFloat "$dv"; dvtest=$floatresult + [ $dvtest -eq 0 ] && errMsg "OSHIFTY=$dv IS NOT A NUMBER" + ;; + zoom[=]*) # output zoom + arg="$1=" + zoom=`echo "$arg" | cut -d= -f2` + # function bc does not seem to like numbers starting with + sign, so strip off + zoom=`echo "$zoom" | sed 's/^[+]\(.*\)$/\1/'` + # zoomtest>0 if floating point number; otherwise peftest=0 + testFloat "$zoom"; zoomtest=$floatresult + zoomtest=`echo "$zoom < 1 && $zoom > -1" | bc` + [ $zoomtest -eq 1 ] && errMsg "ZOOM=$zoom MUST BE GREATER THAN 1 OR LESS THAN -1" + ;; + bgcolor[=]*) # output background color + arg="$1=" + bgcolor=`echo "$arg" | cut -d= -f2` + ;; + skycolor[=]*) # output sky color + arg="$1=" + skycolor=`echo "$arg" | cut -d= -f2` + ;; + vp[=]*) # virtual pixel method + arg="$1=" + vp=`echo "$arg" | cut -d= -f2` + [ "$vp" != "background" -a "$vp" != "dither" -a "$vp" != "edge" -a "$vp" != "mirror" -a "$vp" != "random" -a "$vp" != "tile" -a "$vp" != "transparent" ] && errMsg "VP=$vp IS NOT A VALID VALUE" + ;; + auto[=]*) # output background color + arg="$1=" + auto=`echo "$arg" | cut -d= -f2` + [ "$auto" != "c" -a "$auto" != "zc" -a "$auto" != "out" ] && errMsg "AUTO=$auto IS NOT A VALID VALUE" + ;; + *[=]*) # not valid + errMsg "$1 IS NOT A VALID ARGUMENT" + ;; + *) # end of arguments + break + ;; + esac + shift # next option + done + # + # get infile and outfile + infile=$1 + outfile=$2 +fi + +# setup temporary images and auto delete upon exit +# use mpc/cache to hold input image temporarily in memory +tmpA="$dir/3Drotate_$$.mpc" +tmpB="$dir/3Drotate_$$.cache" +trap "rm -f $tmpA $tmpB; exit 0" 0 +trap "rm -f $tmpA $tmpB; exit 1" 1 2 3 15 + +# test that infile provided +[ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED" +# test that outfile provided +[ "$outfile" = "" ] && errMsg "NO OUTPUT FILE SPECIFIED" + +if convert -quiet -regard-warnings "$infile" +repage "$tmpA" + then + [ "$pef" = "" ] && pef=1 +else + errMsg "--- FILE $infile DOES NOT EXIST OR IS NOT AN ORDINARY FILE, NOT READABLE OR HAS ZERO SIZE ---" +fi + +# get input image width and height +imagesize +maxwidth=`expr $width - 1` +maxheight=`expr $height - 1` + +# deal with auto adjustments to values +if [ "$auto" = "zc" ] + then + du=0 + dv=0 + zoom=1 +elif [ "$auto" = "c" ] + then + du=0 + dv=0 +fi + +# convert offsets of rotation point to relative to pixel 0,0 +di=`echo "scale=10; ($di + (($width - 1) / 2)) / 1" | bc` +dj=`echo "scale=10; ($dj + (($height - 1) / 2)) / 1" | bc` +du=`echo "scale=10; $du / 1" | bc` +dv=`echo "scale=10; $dv / 1" | bc` + +# convert zoom to scale factors +if [ `echo "$zoom >= 1" | bc` -eq 1 ] + then + sx=`echo "scale=10; 1 / $zoom" | bc` + sy=$sx +elif [ `echo "$zoom <= -1" | bc` -eq 1 ] + then + sx=`echo "scale=10; - $zoom / 1" | bc` + sy=$sx +fi + +# Consider the picture placed on the Z=0 plane and the camera a distance +# Zc=f above the picture plane looking straight down at the image center. +# Now the perspective equations (in 3-D) are defined as (x,y,f) = M (X',Y',Z'), +# where the camera orientation matrix M is the identity matrix but with M22=-1 +# because the camera is looking straight down along -Z. +# Thus a reflection transformation relative to the ground plane coordinates. +# Let the camera position Zc=f=(sqrt(ins*ins + inl*inl)) / ( 2 tan(fov/2) ) +# Now we want to rotate the ground points corresponding to the picture corners. +# The basic rotation is (X',Y',Z') = R (X,Y,0), where R is the rotation matrix +# involving pan, tilt and roll. +# But we need to convert (X,Y,0) to (X,Y,1) and also to offset for Zc=f +# First we note that (X,Y,0) = (X,Y,1) - (0,0,1) +# Thus the equation becomes (x,y,f) = M {R [(X,Y,1) - (0,0,1)] - (0,0,Zc)} = MT (X,Y,1) +# But R [(X,Y,1) - (0,0,1)] = R [II (X,Y,1) - S (X,Y,1)] = R (II-S) (X,Y,1), where +# II is the identity matrix and S is an all zero matrix except for S22=1. +# Thus (II-S) is the identity matrix with I22=0 and +# RR = R (II-S) is just R with the third column all zeros. +# Thus we get (x,y,f) = M {RR (X,Y,1) - (0,0,Zc)}. +# But M {RR (X,Y,1) - (0,0,Zc)} = M {RR(X,Y,1) - D (X,Y,1)}, where +# D is an all zero matrix with D22 = Zc = f. +# So that we get M (RR-D) (X,Y,1) = MT (X,Y,1), where +# where T is just R with the third column (0,0,-f), i.e. T02=0, T12=0, T22=-f +# But we need to allow for scaling and offset of the output coordinates and +# conversion from (x,y,f) to (u,v,1)=O and conversion of input coordinates +# from (X,Y,1) to (i,j,1)=I. +# Thus the forward transformation becomes AO=MTBI or O=A'MTBI or O=PI, +# where prime means inverse. +# However, to do the scaling of the output correctly, need to offset by the input +# plus output offsets, then scale, which is all put into A'. +# Thus the forward transformation becomes AO=MTBI or O=A'MTBI where A'=Ai +# but we will merge A'M into Aim +# Thus the inverse transform becomes +# I=QO where Q=P' +# A=output scaling, offset and conversion matrix +# B=input offset and conversion matrix (scaling only needs to be done in one place) +# M=camera orientation matrix +# R=image rotation matrix Rroll Rtilt Rpan +# T=matrix that is R but R33 offset by f + 1 +# O=output coords vector (i,j,1) +# I=input coords vector (u,v,1)=(is,il,1) +# P=forward perspective transformation matrix +# Q=inverse perspective transformation matrix +# +# For a 35 mm camera whose film format is 36mm wide and 24mm tall, when the focal length +# is equal to the diagonal, the field of view is 53.13 degrees and this is +# considered a normal view equivalent to the human eye. +# See http://www.panoramafactory.com/equiv35/equiv35.html +# Max limit on dfov is 180 degrees (pef=3.19) where get single line like looking at picture on edge. +# Above this limit the picture becomes like the angles get reversed. +# Min limit on dfov seems to be slightly greater than zero degrees. +# Practical limits on dfov depend upon orientation angles. +# For tilt=45, this is about 2.5 dfov (pef=2.5). Above this, some parts of the picture +# that are cut off at the bottom, get wrapped and stretched in the 'sky'. + +dfov=`echo "scale=10; 180 * a(36/24) / $pi" | bc -l` +if [ "$pef" = "" ] + then + pfact=1 +elif [ "$pef" = "0" ] + then + pfact=`echo "scale=10; 0.01 / $dfov" | bc` +else + pfact=$pef +fi +#maxpef=`echo "scale=5; 180 / $dfov" | bc` +#echo "maxpef=$maxpef" + +#compute new field of view based upon pef (pfact) +dfov=`echo "scale=10; $pfact * $dfov" | bc` +dfov2=`echo "scale=10; $dfov / 2" | bc` +arg=`echo "scale=10; $pi * $dfov2 / 180" | bc` +sfov=`echo "scale=10; s($arg)" | bc -l` +cfov=`echo "scale=10; c($arg)" | bc -l` +tfov=`echo "scale=10; $sfov / $cfov" | bc -l` +#echo "tfov=$tfov" + +# calculate focal length in same units as wall (picture) using dfov +diag=`echo "scale=10; sqrt(($width * $width) + ($height * $height))" | bc` +focal=`echo "scale=10; ($diag / (2 * $tfov))" | bc -l` +#echo "focal=$focal" + +# calculate forward transform matrix Q + +# define the input offset and conversion matrix +dim=`echo "scale=10; - $di" | bc` +B0=(1 0 $dim) +B1=(0 -1 $dj) +B2=(0 0 1) + +# define the output scaling, offset and conversion matrix inverse Ai and merge with M +# to become Aim +#A0=($sx 0 $sx*(-$du-$di)) +#A1=(0 -$sy $sy*($dv+$dj)) +#A2=(0 0 -$focal) +#M0=(1 0 0) +#M1=(0 1 0) +#M2=(0 0 -1) +aim00=`echo "scale=10; 1 / $sx" | bc` +aim02=`echo "scale=10; -($sx * ($di + $du)) / ($sx * $focal)" | bc` +aim11=`echo "scale=10; -1 / $sy" | bc` +aim12=`echo "scale=10; -($sy * ($dj + $dv)) / ($sy * $focal)" | bc` +aim22=`echo "scale=10; -1 / $focal" | bc` +Aim0=($aim00 0 $aim02) +Aim1=(0 $aim11 $aim12) +Aim2=(0 0 $aim22) + +# now do successive matrix multiplies from right towards left of main equation P=A'RB + +# convert R to T by setting T02=T12=0 and T22=-f +focalm=`echo "scale=10; - $focal" | bc` +T0=(${R0[0]} ${R0[1]} 0) +T1=(${R1[0]} ${R1[1]} 0) +T2=(${R2[0]} ${R2[1]} $focalm) + +# multiply T x B = P +MM3 "${T0[*]}" "${T1[*]}" "${T2[*]}" "${B0[*]}" "${B1[*]}" "${B2[*]}" + +# multiply Aim x P = P +MM3 "${Aim0[*]}" "${Aim1[*]}" "${Aim2[*]}" "${P0[*]}" "${P1[*]}" "${P2[*]}" + +# the resulting P matrix is now the perspective coefficients for the inverse transformation +P00=${P0[0]} +P01=${P0[1]} +P02=${P0[2]} +P10=${P1[0]} +P11=${P1[1]} +P12=${P1[2]} +P20=${P2[0]} +P21=${P2[1]} +P22=${P2[2]} + +# project input corners to output domain +#echo "UL" +i=0 +j=0 +#echo "i,j=$i,$j" +forwardProject $i $j +#echo "u,v=$uu,$vv" +u1=$uu +v1=$vv +#echo "UR" +i=$maxwidth +j=0 +#echo "i,j=$i,$j" +forwardProject $i $j +#echo "u,v=$uu,$vv" +u2=$uu +v2=$vv +#echo "BR" +i=$maxwidth +j=$maxheight +#echo "i,j=$i,$j" +forwardProject $i $j +#echo "u,v=$uu,$vv" +u3=$uu +v3=$vv +#echo "BL" +i=0 +j=$maxheight +#echo "i,j=$i,$j" +forwardProject $i $j +#echo "u,v=$uu,$vv" +u4=$uu +v4=$vv +#echo "C" +#i=`echo "scale=10; $maxwidth / 2" | bc` +#j=`echo "scale=10; $maxheight / 2" | bc` +#echo "i,j=$i,$j" +#forwardProject $i $j +#echo "u,v=$uu,$vv" +#u5=$uu +#v5=$vv + +# unused +: ' +# Now invert P to get Q for the inverse perspective transformation +# Use the Method of the Adjoint Matrix = transpose of matrix of cofactors divided by the determinant +# M3inverse $P00 $P01 $P02 $P10 $P11 $P12 $P20 $P21 $P22 +# +# project output corners to input domain +# UL +#echo "UL 0,0" +#u=$u1 +#v=$v1 +#echo "u,v=$u,$v" +#inverseProject $u $v +#echo "i,j=$ii,$jj" +#echo "UR 255,0" +#u=$u2 +#v=$v2 +#echo "u,v=$u,$v" +#inverseProject $u $v +#echo "i,j=$ii,$jj" +#echo "BR 255,255" +#u=$u3 +#v=$v3 +#echo "u,v=$u,$v" +#inverseProject $u $v +#echo "i,j=$ii,$jj" +#echo "BL 0,255" +#u=$u4 +#v=$v4 +#echo "u,v=$u,$v" +#inverseProject $u $v +#echo "i,j=$ii,$jj" +#echo "C 127.5,127.5" +#u=$u5 +#v=$v5 +#echo "u,v=$u,$v" +#inverseProject $u $v +#echo "i,j=$ii,$jj" +' + +# deal with adjustments for auto settings +# first get the bounding box dimensions +uArr=($u1 $u2 $u3 $u4) +vArr=($v1 $v2 $v3 $v4) +index=0 +umin=1000000 +umax=-1000000 +vmin=1000000 +vmax=-1000000 +while [ $index -lt 4 ] + do + [ `echo "${uArr[$index]} < $umin" | bc` -eq 1 ] && umin=${uArr[$index]} + [ `echo "${uArr[$index]} > $umax" | bc` -eq 1 ] && umax=${uArr[$index]} + [ `echo "${vArr[$index]} < $vmin" | bc` -eq 1 ] && vmin=${vArr[$index]} + [ `echo "${vArr[$index]} > $vmax" | bc` -eq 1 ] && vmax=${vArr[$index]} + index=`expr $index + 1` +done +delu=`echo "scale=10; $umax - $umin + 1" | bc` +delv=`echo "scale=10; $vmax - $vmin + 1" | bc` +if [ "$auto" = "c" ] + then + offsetu=`echo "scale=10; ($width - $delu) / 2" | bc` + offsetv=`echo "scale=10; ($height - $delv) / 2" | bc` + u1=`echo "scale=0; $offsetu + ($u1 - $umin)" | bc` + v1=`echo "scale=0; $offsetv + ($v1 - $vmin)" | bc` + u2=`echo "scale=0; $offsetu + ($u2 - $umin)" | bc` + v2=`echo "scale=0; $offsetv + ($v2 - $vmin)" | bc` + u3=`echo "scale=0; $offsetu + ($u3 - $umin)" | bc` + v3=`echo "scale=0; $offsetv + ($v3 - $vmin)" | bc` + u4=`echo "scale=0; $offsetu + ($u4 - $umin)" | bc` + v4=`echo "scale=0; $offsetv + ($v4 - $vmin)" | bc` +elif [ "$auto" = "zc" ] + then + if [ `echo "$delu > $delv" | bc` -eq 1 ] + then + del=$delu + offsetu=0 + offsetv=`echo "scale=10; ($height - ($delv * $width / $delu)) / 2" | bc` + else + del=$delv + offsetu=`echo "scale=10; ($width - ($delu * $height / $delv)) / 2" | bc` + offsetv=0 + fi + u1=`echo "scale=0; $offsetu + (($u1 - $umin) * $width / $del)" | bc` + v1=`echo "scale=0; $offsetv + (($v1 - $vmin) * $height / $del)" | bc` + u2=`echo "scale=0; $offsetu + (($u2 - $umin) * $width / $del)" | bc` + v2=`echo "scale=0; $offsetv + (($v2 - $vmin) * $height / $del)" | bc` + u3=`echo "scale=0; $offsetu + (($u3 - $umin) * $width / $del)" | bc` + v3=`echo "scale=0; $offsetv + (($v3 - $vmin) * $height / $del)" | bc` + u4=`echo "scale=0; $offsetu + (($u4 - $umin) * $width / $del)" | bc` + v4=`echo "scale=0; $offsetv + (($v4 - $vmin) * $height / $del)" | bc` +fi +# +# now do the perspective distort +if [ "$auto" = "out" ] + then + distort="+distort" +else + distort="-distort" +fi + +im_version=`convert -list configure | \ + sed '/^LIB_VERSION_NUMBER /!d; s//,/; s/,/,0/g; s/,0*\([0-9][0-9]\)/\1/g' | head -n 1` +if [ "$im_version" -lt "06030600" ] + then + convert $tmpA -virtual-pixel $vp -background $bgcolor \ + -mattecolor $skycolor $distort Perspective \ + "0,0 $maxwidth,0 $maxwidth,$maxheight 0,$maxheight $u1,$v1 $u2,$v2 $u3,$v3 $u4,$v4" $outfile +else + convert $tmpA -virtual-pixel $vp -background $bgcolor \ + -mattecolor $skycolor $distort Perspective \ + "0,0 $u1,$v1 $maxwidth,0 $u2,$v2 $maxwidth,$maxheight $u3,$v3 0,$maxheight $u4,$v4" $outfile +fi +exit 0 diff --git a/bevelborder b/bevelborder new file mode 100755 index 0000000..6f6ec2a --- /dev/null +++ b/bevelborder @@ -0,0 +1,295 @@ +#!/bin/bash +# +# Developed by Fred Weinhaus 7/16/2010 .......... revised 6/30/2011 +# +# USAGE: bevelborder [-s size] [-m method] [-p percent] [-c contrast] [-b bcolor] [-a amount] [-t type] infile outfile +# USAGE: bevelborder [-h or -help] +# +# OPTIONS: +# +# -s size size of border in pixels; same in both dimensions; +# default equals 10% of min(imagewidth, imageheight) +# -m method bevel method; choices are: outer, inner or split; +# default=outer +# -p percent split percent between outer and inner bevel; +# 100 is outer bevel only; 0 is inner bevel only; +# default=50 applies only to method=split +# -c contrast contrast percent for bevel; 0<=integer<=100; +# default=50 +# -b bcolor border coloring; any IM opaque color is allowed; +# default is no coloring +# -a amount amount of border coloring; 0<=integer<=100; +# default=25 +# -t type type of compose; hardlight, linearlight or +# vividlight; default=hardlight +# +### +# +# NAME: BEVELBORDER +# +# PURPOSE: To applies a bevel effect to the border of an image. +# +# DESCRIPTION: BEVELBORDER applies a bevel effect to the border of an image. +# The bevel can be an outer bevel (raised effect), an inner bevel (depressed +# effect) or a split (mix) of the two. The border may also be colorized. +# +# +# ARGUMENTS: +# +# -s size ... SIZE is the dimensions of the border region in pixels. The same +# value is used in both dimensions. Values are integers greater than 0. The +# default is 10% of the min(width,height) of the image. +# +# -m method ... METHOD is the bevel method. Choices are: outer, which makes a +# raised effect; inner, which makes a depressed effect; and split, which is +# part raised and part depressed. The amount of each is controlled by the +# percent argument. The default is outer. +# +# -p percent ... PERCENT is the percent split between outer and inner bevels. +# Values are integers such that 0<=percent<=100. A value of 100 is full outer +# bevel. A value of 0 is full inner bevel. The default is 50 and only applies +# when the method=split. +# +# -c contrast ... CONTRAST percent for innerbevel or outerbevel. Values are +# integers between 0 and 100. The default=50. +# +# -b bcolor ... BCOLOR is the bevel colorization color. Any valid opaque IM +# color is allowed. The default is no additional colorization. +# +# -a amount ... AMOUNT of colorization. Values are integers between 0 and 100. +# The default=25. +# +# -t type ... TYPE of compose. Choices are: hardlight, linearlight and +# vividlight. The default=hardlight +# +# REQUIREMENTS: IM 6.5.9.0 or higher due to the use of -brightness-contrast. +# +# CAVEAT: No guarantee that this script will work on all platforms, +# nor that trapping of inconsistent parameters is complete and +# foolproof. Use At Your Own Risk. +# +###### +# + +# set default values +size="" # bevel border amount in pixels +method="outer" # outer, inner, split +percent=50 # split percent; 0 to 100; 100 is outer; 0 is inner +contrast=50 # bevel contrast percent +bcolor="" # bevel color +amount=25 # amount of coloring; 0<=integer<=100 +type="hardlight" # bevel compose method + +# set directory for temporary files +dir="." # suggestions are dir="." or dir="/tmp" + +# set up functions to report Usage and Usage with Description +PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path +PROGDIR=`dirname $PROGNAME` # extract directory of program +PROGNAME=`basename $PROGNAME` # base name of program +usage1() + { + echo >&2 "" + echo >&2 "$PROGNAME:" "$@" + sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" + } +usage2() + { + echo >&2 "" + echo >&2 "$PROGNAME:" "$@" + sed >&2 -n '/^######/q; /^#/!q; s/^#*//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" + } + + +# function to report error messages +errMsg() + { + echo "" + echo $1 + echo "" + usage1 + exit 1 + } + + +# function to test for minus at start of value of second part of option 1 or 2 +checkMinus() + { + test=`echo "$1" | grep -c '^-.*$'` # returns 1 if match; 0 otherwise + [ $test -eq 1 ] && errMsg "$errorMsg" + } + +# test for correct number of arguments and get values +if [ $# -eq 0 ] + then + # help information + echo "" + usage2 + exit 0 +elif [ $# -gt 16 ] + then + errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---" +else + while [ $# -gt 0 ] + do + # get parameter values + case "$1" in + -h|-help) # help information + echo "" + usage2 + exit 0 + ;; + -s) # get size + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID SIZE SPECIFICATION ---" + checkMinus "$1" + size=`expr "$1" : '\([0-9]*\)'` + [ "$size" = "" ] && errMsg "--- SIZE=$size MUST BE A NON-NEGATIVE INTEGER VALUE (with no sign) ---" + testA=`echo "$size <= 0" | bc` + [ $testA -eq 1 ] && errMsg "--- SIZE=$size MUST BE A POSITIVE INTEGER ---" + ;; + -m) # get method + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID METHOD SPECIFICATION ---" + checkMinus "$1" + # test type values + method=`echo "$1" | tr "[:upper:]" "[:lower:]"` + case "$method" in + outer|inner|split) ;; # do nothing - valid type + *) errMsg "--- METHOD=$method IS NOT A VALID VALUE ---" ;; + esac + ;; + -p) # get percent + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID PERCENT SPECIFICATION ---" + checkMinus "$1" + percent=`expr "$1" : '\([0-9]*\)'` + [ "$percent" = "" ] && errMsg "--- PERCENT=$percent MUST BE A NON-NEGATIVE INTEGER ---" + testA=`echo "$percent < 0" | bc` + testB=`echo "$percent > 100" | bc` + [ $testA -eq 1 -o $testB -eq 1 ] && errMsg "--- PERCENT=$percent MUST BE AN INTEGER BETWEEN 0 AND 100 ---" + ;; + -c) # get contrast + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID CONTRAST SPECIFICATION ---" + checkMinus "$1" + contrast=`expr "$1" : '\([0-9]*\)'` + [ "$contrast" = "" ] && errMsg "--- CONTRAST=$contrast MUST BE A NON-NEGATIVE INTEGER ---" + testA=`echo "$contrast < 0" | bc` + testB=`echo "$contrast > 100" | bc` + [ $testA -eq 1 -o $testB -eq 1 ] && errMsg "--- CONTRAST=$contrast MUST BE AN INTEGER BETWEEN 0 AND 100 ---" + ;; + -b) # get bcolor + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID BCOLOR SPECIFICATION ---" + checkMinus "$1" + bcolor="$1" + ;; + -a) # get amount + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID AMOUNT SPECIFICATION ---" + checkMinus "$1" + amount=`expr "$1" : '\([0-9]*\)'` + [ "$amount" = "" ] && errMsg "--- AMOUNT=$amount MUST BE A NON-NEGATIVE INTEGER ---" + testA=`echo "$amount < 0" | bc` + testB=`echo "$amount > 100" | bc` + [ $testA -eq 1 -o $testB -eq 1 ] && errMsg "--- AMOUNT=$amount MUST BE AN INTEGER BETWEEN 0 AND 100 ---" + ;; + -t) # get type + shift # to get the next parameter + # test if parameter starts with minus sign + errorMsg="--- INVALID TYPE SPECIFICATION ---" + checkMinus "$1" + # test type values + type=`echo "$1" | tr "[:upper:]" "[:lower:]"` + case "$type" in + hardlight|linearlight|vividlight) ;; # do nothing - valid type + *) errMsg "--- METHOD=$method IS NOT A VALID VALUE ---" ;; + esac + ;; + -) # STDIN and end of arguments + break + ;; + -*) # any other - argument + errMsg "--- UNKNOWN OPTION ---" + ;; + *) # end of arguments + break + ;; + esac + shift # next option + done + # + # get infile and outfile + infile=$1 + outfile=$2 +fi + +# test that infile provided +[ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED" + +# test that outfile provided +[ "$outfile" = "" ] && errMsg "NO OUTPUT FILE SPECIFIED" + + +tmpA="$dir/bevelborder_$$.mpc" +tmpB="$dir/bevelborder_$$.cache" +trap "rm -f $tmpA $tmpB; exit 0" 0 +trap "rm -f $tmpA $tmpB; exit 1" 1 2 3 15 + + +# read the input image into the TMP cached image. +convert -quiet -regard-warnings "$infile" +repage "$tmpA" || + errMsg "--- FILE $infile NOT READABLE OR HAS ZERO SIZE ---" + +# set default size +if [ "$size" = "" ]; then + size=`convert $tmpA -ping -format "%[fx:floor(0.1*min(w,h))]" info:` + wsize=$size + hsize=$size +fi + +# get input image size +ww=`convert $infile -ping -format "%w" info:` +hh=`convert $infile -ping -format "%h" info:` +wd=`convert xc: -format "%[fx:$ww-2*$size]" info:` +ht=`convert xc: -format "%[fx:$hh-2*$size]" info:` + +# adjust contrast and transparency to fractions +contr1=`convert xc: -format "%[fx:$contrast-100]" info:` + +# setup bevel parameters +if [ "$method" = "outer" ]; then + wsize2=$size + hsize2=0 +elif [ "$method" = "inner" ]; then + hsize2=$size + wsize2=0 +elif [ "$method" = "split" ]; then + wsize2=`convert xc: -format "%[fx:floor($percent*$size/100)]" info:` + hsize2=$(($size-$wsize2)) +fi +# echo "ww=$ww; hh=$hh; wd=$wd; ht=$ht; wsize2=$wsize2; hsize2=$hsize2" + +if [ "$bcolor" = "" ]; then + colorize="" +else + colorize="-fill $bcolor -colorize $amount%" +fi + +# process image +convert $tmpA -size ${wd}x${ht} xc:"gray(50%)" \ +\( -clone 1 -frame ${size}x${size}+${wsize2}+${hsize2} \ +-auto-level -black-threshold 25% -white-threshold 75% $colorize \ +-brightness-contrast 0,${contr1}% \ +-clone 1 -gravity center -composite \) \ +-delete 1 -compose $type -composite \ +$outfile + +exit 0 diff --git a/breaker.py b/breaker.py new file mode 100755 index 0000000..99e711b --- /dev/null +++ b/breaker.py @@ -0,0 +1,394 @@ +#!/usr/bin/python2.7 +from subprocess import call, Popen, PIPE +import urllib +import urllib2 +import os +import sys +import random +import re +import time +urlencode = urllib.urlencode +urlopen = urllib2.urlopen +Request = urllib2.Request + +WORKING_DIR = "/tmp" +BIN_CONVERT = "/usr/bin/convert" +BIN_IDENTIFY = "/usr/bin/identify" +DEFAULT_FINALFORMAT = "png"; + +SUBTLE_BREAK_MARK = 'pron' +EXTREME_BREAK_MARK = 'sugar' + +HEADER_OFFSET = 5000 + +MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 + +# 'CLASSIC':'jpg', +# 'REDUX':'pcds', +# 'BLURRY_BREAK':'viff', +# 'BLURRY_BREAK_2':'mat', +# 'SWIPE':'miff', +# 'RGB_WASH':'psd', +# 'RGB_WASH_2':'psb', +# 'NOISY_BREAK':'palm', +# 'NOISY_BREAK_2':'fig', +# 'BROKEN_VIGNETTE':'pbm', +# 'FAX_MACHINE':'cals', +# 'STRIPES':'exr', +# 'PHOTOCOPY':'art', +TEST_PARAMS = { + "url" : "http://i.asdf.us/im/27/1424816234661dumpfmpfifferkinggr_1424816412_pfifferking.gif" , + "breaktype" : "RGB_WASH", + "finalformat" : "png", + "breakmode" : "extreme", + "breakangle" : "10", + "username" : "donkey", + "expanded" : "false" +} + +def bool_correct(s): + if re.match(r'^false$', s, re.IGNORECASE): + return False + elif re.match(r'^true$', s, re.IGNORECASE): + return True + else: + return s + +#{{{Utility functions +class dotdict(dict): + """dot.notation access to dictionary attributes""" + def __getattr__(self, attr): + return self.get(attr) + __setattr__= dict.__setitem__ + __delattr__= dict.__delitem__ +def sanitize (str): + return re.sub(r'\W+', '', str) + +def now(): + return int(time.time()) + +def browser_request (url, data=None): + headers = { + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', + 'Accept': '*/*', + } + try: + req = Request(url, data, headers) + response = urlopen(req) + except IOError, e: + if hasattr(e, 'code'): + sys.stderr.write( '%s - ERROR %s' % (url, e.code) ) + raise; + return None + else: + return response + +def download(url, destination, max_size=MAX_SIZE): + response = browser_request(url, None) + rawimg = response.read() + if len(rawimg) == 0: + sys.stderr.write("got zero-length file") + raise; + if len(rawimg) > max_size: + sys.stderr.write("file too big: max size {} KB / {} is {} KB".format( + str(MAX_SIZE/1024), + destination, + str(len(rawimg)/1024) + )) + raise; + f = open(destination, "w") + f.write(rawimg) + f.close() + +def dimensions (filepath): + #works in lieu of a mimetype check (it reads the header as well) + ident = (Popen([BIN_IDENTIFY, filepath], stdout=PIPE).communicate()[0]).split(" ") + return ident[2].split("x") + +def file_size (filepath): + try: + return os.stat(file)[6] + except Exception as e: + sys.stderr.write(str(e)) + raise; + +def gif_frames(filepath): + try: + info = Popen([BIN_IDENTIFY,filepath], stdout=PIPE).communicate()[0] + frames = filter((lambda x: x), map( + (lambda x: x.split(" ")[0]), + (info).split('\n') + )) + return frames + except Exception as e: + sys.stderr.write(str(e)) + raise; +#}}} + +class Breaker(): + def __init__(self, params): + self.params = {} + self.now = now() + self.tag = "imBreak" + self.commands = []; + self.required_keys = [ + "url", + "breaktype", + "finalformat", + "breakmode", + "breakangle", + "username", + "expanded" + ] + self.files_created = [] + for k in self.required_keys: + if k in params: + if k == 'breaktype': + self.params['breaktype'] = self._get_breaktype(params[k]) + elif k == 'url': + self.params[k] = params[k] + else: + self.params[k] = bool_correct(sanitize(params[k])) + else: + self.params[k] = False; + + + self.params = dotdict(self.params) + + self.basename, self.first_format = self._get_filename(); + self.downloaded_file = os.path.join(WORKING_DIR, "IMBREAKTMP{}.{}".format(self.basename, self.first_format)) + + try: + download(self.params.url, self.downloaded_file) + self.files_created.append(self.downloaded_file) + except Exception as e: + sys.stderr.write(str(e)) + raise; + self.gif_frames = gif_frames(self.downloaded_file) + self.gif_frames = self.gif_frames if len(self.gif_frames) > 1 else False + self.width, self.height = dimensions(self.downloaded_file) + + if not self.params.finalformat: + self.params.finalformat = DEFAULT_FINALFORMAT + if self.gif_frames: + self.params.finalformat = 'gif' + if self.params.breaktype == 'miff': + self.params.finalformat = 'jpg' + self.params.breakmode = 'subtle' + #final filepath is stored in self.filepath + self.filename = "{}.{}".format(self.basename, self.params.finalformat) + self.filepath = os.path.join(WORKING_DIR, self.filename) + self.conversion_file = os.path.join(WORKING_DIR, "IMBREAKTMP{}.{}".format(self.basename, self.params.breaktype)) + + def _get_breaktype(self, key): + #{{{ conversion table + breaktypeTranslate = { + 'CLASSIC':'jpg', + 'REDUX':'pcds', + 'BLURRY_BREAK':'viff', + 'BLURRY_BREAK_2':'mat', + 'SWIPE':'miff', + 'RGB_WASH':'psd', + 'RGB_WASH_2':'psb', + 'NOISY_BREAK':'palm', + 'NOISY_BREAK_2':'fig', + 'BROKEN_VIGNETTE':'pbm', + 'FAX_MACHINE':'cals', + 'STRIPES':'exr', + 'PHOTOCOPY':'art', + } + #}}} + return breaktypeTranslate[key] + + def _get_filename (self): + url = self.params.url + name_part = ""; + file_format = ""; + if "?" in url: + url = url.split("?")[0] + if "/" in url: + url = urllib.unquote(url).replace(" ","") + name_part = url.split("/")[-1] + try: + parts = name_part.split(".") + name_part = sanitize(parts[-2]) + file_format = sanitize(parts[-1]) + if not name_part or not file_format: + sys.stderr.write( "Incompatible input file type") + raise; + except Exception as e: + sys.stderr.write( "Incompatible input file type") + raise; + else: + sys.stderr.write( "Incompatible url") + raise; + if (len(name_part) > 20): + name_part = name_part[:-20] + return "{}{}_{}_{}".format(self.tag, name_part, self.now, self.params.username or ""), file_format + +#{{{#########rotatefunctions####################################### + def _rotate(self): + try: + call([BIN_CONVERT,self.downloaded_file,"-rotate",self.params.breakangle,"+repage",self.downloaded_file]) + except Exception as e: + sys.stderr.write(str(e)) + raise; + def _rotate_back(self): + try: + angle = str(360-int(self.params.breakangle)) + call([BIN_CONVERT,self.filepath,"-rotate",angle,"+repage",self.filepath]) + except Exception as e: + sys.stderr.write(str(e)) + raise; + if not self.params.expanded: + try: + call( + [BIN_CONVERT,self.filepath,"-gravity","Center","-crop","{}x{}+0+0".format( + self.width, self.height),"+repage",self.filepath + ]) + except Exception as e: + sys.stderr.write(str(e)) + raise; +#}}} + def _subtle_break(self): + #assume the header is no longer than HEADER_OFFSET bytes + breakpoint = random.randint(HEADER_OFFSET, len(self.file_data)) + newfile = "" + newfile = self.file_data[0:breakpoint]; + newfile += SUBTLE_BREAK_MARK; + newfile += self.file_data[breakpoint:] + self.file_data = newfile[0:len(self.file_data)] + + def _extreme_break(self): + increment = len(self.file_data)/10; + i = 0 + newfile = ""; + for b in self.file_data: + if i > HEADER_OFFSET and not (i % increment): + b += EXTREME_BREAK_MARK + newfile += b + i += 1 + self.file_data = newfile[0:len(self.file_data)] + + def _choose_frame(self): + frame = random.choice(self.gif_frames) + try: + call([BIN_CONVERT, frame, self.downloaded_file]) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + def _enforce_jpg(self): + if self.params.breaktype in [ "exr", "bmp", "miff" ] and not re.match(r'jpe?g', self.first_format, re.IGNORECASE): + jpg_file = os.path.join(WORKING_DIR, "{}.{}".format(self.basename, "jpg")) + try: + call([BIN_CONVERT,self.downloaded_file,jpg_file]) + call(["rm",self.downloaded_file]) + self.downloaded_file = jpg_file + except Exception as e: + sys.stderr.write(str(e)) + raise; + + def _first_conversion(self): + if self.first_format == self.params.breaktype: + self.downloaded_file = self.conversion_file + return + try: + call([BIN_CONVERT, self.downloaded_file, self.conversion_file]); + self.files_created.append(self.conversion_file) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + def _read_data(self, filepath): + f = open(filepath, 'r'); + data = f.read() + f.close() + return data + + def _prepare_filedata(self): + if self.gif_frames: + self._choose_frame() + if self.params.breakangle: + self._rotate() + self._enforce_jpg(); + self._first_conversion(); + self.file_data = self._read_data(self.conversion_file) + if not self.file_data: + sys.stderr.write("Unable to get file_data") + raise; + + def _add_false_data(self, breakmode): + if breakmode == "subtle": + self._subtle_break() + elif breakmode == "extreme": + self._extreme_break() + f = open(self.conversion_file, 'w') + f.write(self.file_data) + f.close(); + +#{{{ SHRINK (UNUSED) + def _shrink(self): + cmd = [ BIN_CONVERT, "-resize", "500x500", self.downloaded_file, self.downloaded_file ]; + try: + call(cmd) + self.commands.append(" ".join(cmd)); + except Exception as e: + sys.stderr.write(str(e)) + raise; +#}}} + + def _final_conversion(self): + cmd = [BIN_CONVERT, self.conversion_file, self.filepath] + try: + call(cmd) + self.commands.append(" ".join(cmd)); + except Exception as e: + sys.stderr.write(str(e)) + raise; + #handle multiple files created by psd and psb + def psd_psbfilepath(num): + return os.path.join(WORKING_DIR, "{}-{}.{}".format(self.basename, num, self.params.finalformat)) + if self.params.breaktype == 'psd': + cmd = ['mv', psd_psbfilepath(1), self.filepath] + try: + call(cmd) + self.commands.append(" ".join(cmd)); + except Exception as e: + sys.stderr.write(str(e)) + raise + self.files_created.append(psd_psbfilepath(0)) + if self.params.breaktype == 'psb': + cmd = ['mv', psd_psbfilepath(0), self.filepath] + try: + call(cmd) + self.commands.append(" ".join(cmd)); + except Exception as e: + sys.stderr.write(str(e)) + raise + self.files_created.append(psd_psbfilepath(1)) + + if self.params.breakangle: + self._rotate_back() + + def _cleanup(self): + cmd = ["rm"]+self.files_created + try: + call(cmd) + self.commands.append(" ".join(cmd)); + except Exception as e: + sys.stderr.write(str(e)) + raise + + def create(self, breakmode=""): + if not breakmode: breakmode = self.params.breakmode + self._prepare_filedata(); + self._add_false_data(breakmode); + self._final_conversion() + self._cleanup() + + +if __name__ == "__main__": + b = Breaker(TEST_PARAMS) + b.create(); + print b.filepath @@ -0,0 +1,31 @@ +import MySQLdb +USER = "asdfus" +PASSWORD = "urJJwLjdO9GPuOxk" +DATABASE = "asdfus" + +class db: + def __init__ (self): + self.conn = None + self.cursor = None + self.connect() + + def connect (self): + self.conn = MySQLdb.connect (host = "localhost", + user = USER, + passwd = PASSWORD, + db = DATABASE + ) + self.cursor = self.conn.cursor () + + def execute (self,sql,args=()): + try: + self.cursor.execute(sql,args) + except MySQLdb.Error, e: + print "Error %d: %s" % (e.args[0], e.args[1]) + # sys.exit (1) + self.connect() + self.cursor.execute(sql,args) + + def lastinsertid (self): + return DB.conn.insert_id() + diff --git a/gradient.py b/gradient.py new file mode 100755 index 0000000..fbdba3f --- /dev/null +++ b/gradient.py @@ -0,0 +1,257 @@ +#!/usr/bin/python2.7
+import re
+import time
+from subprocess import call
+import simplejson as json
+import sys
+import os
+import db
+import sha
+#{{{ logging
+#import logging
+#logger = logging.getLogger('gradient')
+#logging.FileHandler('/var/www/cache/gradient.log')
+#formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s')
+#hdlr.setFormatter(formatter)
+#logger.addHandler(hdlr)
+#logger.setLevel(logging.WARNING)
+#}}}
+
+BASE_URL = "http://localhost:8080/"
+PARAM_LIST = [
+ "width", "height",
+ "color1", "color2",
+ "stripes",
+ "stripenumber", "stripeintensity",
+ "blurriness",
+ "contrast",
+ "brightness", "saturation", "hue",
+ "halftone",
+ "bevel", "percentbeveled",
+ "rotate", "flip", "flop", "tilt",
+ "filetype",
+ "gradienttype",
+ "username",
+]
+BIN_CONVERT = "/usr/bin/convert"
+BIN_IDENTIFY = "/usr/bin/identify"
+BEVELBORDER = "./bevelborder"
+WEB_ADDR = "http://i.asdf.us"
+DEFAULT_FORMAT = "png"
+DEFAULT_COLORS = {
+ "color1" : "white",
+ "color2" : "black",
+};
+
+WORKING_DIR = '/tmp'
+DEFAULT_WIDTH = "200"
+DEFAULT_HEIGHT = "200"
+
+REMOTE_ADDRESS = ""
+
+BEVELVALUES = {
+ "flatout": ["-s","-m","outer"],
+ "flatinner": ["-s","-m","inner"],
+ "evenlyframed": ["-s ", "-m ", "split"],
+ "biginner": ["-s","-m","outer","-c","50","-b","red","-a","25"],
+ "bigouter": ["-s","-m","split","-c","50","-b","red","-a","25"],
+ "dramaticflatout": ["-s","-m","outer","-a","25","-b","blue"],
+ "dramaticflatinner": ["-s","-m","outer","-a","25","-b","blue"],
+ }
+BEVEL_DEFAULT = "12";
+
+HALFTONEVALUES = {
+ "checkeredfade": "h6x6a",
+ "etchedtransition": "o8x8",
+ "bendaydots": "h16x16o",
+ "smallerdots1": "h8x8o",
+ "smallerdots2": "c7x7w",
+ "flatstripes": "o2x2",
+ }
+
+def sanitize (s):
+ return re.sub(re.compile(r'\W+'), '', s)
+
+def is_number(s):
+ try:
+ return int(s)
+ except (ValueError, TypeError):
+ return False
+
+class Gradient:
+ def __init__(self, form):
+ self.now = int(time.time())
+ self.tag = "imGradient"
+ self.commands = []
+ self.filename = ""
+ self.filepath = ""
+
+ params = {}
+ for key in PARAM_LIST:
+ if key in form:
+ if key in ['color1', 'color2']:
+ params[key] = form[key]
+ #params[key] = form[key].value
+ else:
+ params[key] = sanitize(form[key])
+ #params[key] = sanitize(form[key].value)
+
+ if key in ['rotate','tilt','blurriness','stripenumber','stripeintensity']:
+ params[key] = params[key] if is_number(params[key]) else ""
+ elif key in ['brightness', 'contrast', 'hue']:
+ if not is_number(params[key]) or params[key] == "100": params[key] = ""
+ else:
+ params[key] = ""
+ params['width'] = params['width'] if is_number(params['width']) else DEFAULT_WIDTH
+ params['height'] = params['height'] if is_number(params['height']) else DEFAULT_HEIGHT
+ params["color1"] = params["color1"] or DEFAULT_COLORS["color1"];
+ params["color2"] = params["color2"] or DEFAULT_COLORS["color2"];
+ self.params = params
+
+ def newfilename(self):
+ return "{}{}-{}_{}_{}.{}".format(
+ self.tag,
+ self.params['color1'].replace('#',''),
+ self.params['color2'].replace('#',''),
+ self.now,
+ self.params['username'],
+ self.params['filetype'] or DEFAULT_FORMAT,
+ )
+
+ def buildCmd(self, filename, directory=WORKING_DIR):
+ cmd = [BIN_CONVERT]
+ cmd.extend([
+ '-size',
+ "{}x{}".format(self.params["width"],self.params["height"])
+ ])
+ if self.params['rotate']: cmd.extend(["-rotate", self.params["rotate"]])
+ if self.params['tilt']: cmd.extend(["-distort","SRT",self.params['tilt']])
+ if self.params['flip'] == "true": cmd.append("-flip")
+ if self.params['flop'] == "true": cmd.append("-flop")
+ if self.params['contrast']: cmd.extend(["-contrast-stretch", self.params['contrast']])
+ gradients = {
+ "canvas" : ["canvas:{}".format(self.params['color1'])],
+ "radial" : [
+ "radial-gradient:{}-{}".format( self.params['color1'], self.params['color2'])
+ ],
+ "colorspace" : [
+ "-colorspace",
+ "Gray",
+ "plasma:{}-{}".format(self.params['color1'], self.params['color2'])
+ ],
+ "mirrored" : [
+ "plasma:{}-{}".format(self.params['color1'], self.params['color2']),
+ "\(","+clone","-flop","\)",
+ "append"
+ ],
+ "plasmawash" : [
+ "plasma:{}-{}".format(self.params['color1'], self.params['color2']),
+ "-set","colorspace","HSB"
+ ],
+ "gradientwash" : [
+ "gradient:{}-{}".format(self.params['color1'], self.params['color2']),
+ "-set","colorspace","HSB"
+ ],
+ "noise" : ["xc:","+noise","Random","-virtual-pixel","tile"]
+ }
+ if self.params["gradienttype"] in gradients:
+ cmd.extend(gradients[self.params['gradienttype']])
+ else:
+ cmd.append("gradient:{}-{}".format(self.params['color1'], self.params['color2']))
+
+ if self.params['blurriness']:
+ cmd.extend(["-blur","0x{}".format(self.params["blurriness"]),"-auto-level"])
+
+ if self.params['stripes'] == "true" and len(self.params['stripenumber']):
+ cmd.extend(["-function","Sinusoid"])
+ if self.params['stripeintensity']:
+ cmd.append("{},{}".format(self.params['stripenumber'],self.params["stripeintensity"]))
+ else:
+ cmd.append(self.params['stripenumber'])
+ if self.params["halftone"] in HALFTONEVALUES:
+ cmd.extend([
+ "-ordered-dither",
+ HALFTONEVALUES[self.params["halftone"]]
+ ])
+ cmd += [
+ '-modulate',
+ "{},{},{}".format(
+ self.params['brightness'] or "100",
+ self.params['saturation'] or "100",
+ self.params['hue'] or "100")
+ ]
+ cmd.append(os.path.join(directory,filename));
+# logger.warning('in buildCmd: {}' % cmd)
+ try:
+ call(cmd)
+ self.commands.append(" ".join(cmd));
+ except Exception as e:
+ sys.stderr.write( "ERROR:{}".format(e))
+ raise;
+ return cmd
+
+
+ def makeBevel(self, filename, directory):
+ def get_bevelvalue(key, bevpercentval):
+ bevel_args = BEVELVALUES[key]
+ bevel_args.insert(1, bevpercentval)
+ return bevel_args
+ if self.params['percentbeveled']:
+ try:
+ w, h = map(int, (self.params['width'], self.params['height']))
+ except Exception as e:
+ sys.stderr.write( "ERROR: {}".format(e))
+ exit(1);
+ if h >= w:
+ bevpercentval = str(int(self.params['percentbeveled'])*0.005*int(h))
+ else:
+ bevpercentval = str(int(self.params['percentbeveled'])*0.005*int(w))
+ else:
+ bevpercentval = BEVEL_DEFAULT
+
+ bevel = get_bevelvalue(self.params['bevel'], bevpercentval)
+ cmd = [BEVELBORDER]
+ cmd += bevel
+ cmd += [ os.path.join(directory,filename), os.path.join(directory, filename) ]
+ try:
+ call(cmd)
+ self.commands.append(" ".join(cmd))
+ except Exception as e:
+ sys.stderr.write( "ERROR: {}".format(e))
+ raise;
+
+ def create(self, directory=WORKING_DIR):
+ self.filename = self.newfilename()
+ self.filepath = os.path.join(directory, self.filename)
+ self.buildCmd(self.filename, directory)
+ if self.params['bevel'] in BEVELVALUES:
+ self.makeBevel(self.filename, directory)
+ self.output = [
+ str(os.stat(self.filepath)[6]),
+ "{}px".format(self.params["width"]),
+ "{}px".format(self.params["height"]),
+ ]
+
+TEST_FORM = {
+ "width" : "200",
+ "color1" : "#ffdead",
+ "color2" : "blue",
+ "stripes" : "true",
+ "stripenumber" : "20",
+ "gradienttype" : "radial",
+ "stripeintensity" : "20",
+ "halftone" : "checkeredfade",
+ "percentbeveled" : "30",
+ "flip" : "true",
+ "bevel" : "flatout",
+ "rotate" : "20",
+ "height" : "200",
+ "filetype" : "jpg",
+ "username" : "whatever"
+}
+if __name__ == "__main__":
+ g = Gradient(TEST_FORM);
+ g.create();
+ print g.now
+ print " ".join(g.commands)
+ print "\n".join(g.output)
@@ -0,0 +1,241 @@ +#!/bin/bash +# +# Developed by Fred Weinhaus 10/22/2007 .......... revised 1/1/2008 +# +# USAGE: grid [-s spacing] [-c color] [-t thickness] [-o opacity] infile outfile +# USAGE: grid [-h or -help] +# +# OPTIONS: +# +# -s spacing x,y spacing between grid lines; default=16,16 or 16; +# second number is defaulted to the first +# -c color color of grid lines; default="black" +# -t thickness thickness of grid lines; default=1 +# -o opacity opacity of grid lines opacity between 0.0 and 1.0; +# opacity=0 is transparent; opacity=1 is opaque; +# default=1 +# +### +# +# NAME: GRID +# +# PURPOSE: To superimpose a set of horizontal and/or vertical grid lines +# on an image. +# +# DESCRIPTION: GRID superimposes a set of horizontal and/or vertical grid +# lines on an image. Parameters are available to select the grid line color, +# thickness and opacity. +# +# +# OPTIONS: +# +# -s spacing ... SPACING specifies the horizontal (x) and vertical (y) grid +# offset between lines. Spacing must be provided as integer values. If the +# second value is left off, then it will be set identical to the first. If +# only vertical lines are desired, set the horizontal (x) spacing larger +# than the width of the image. If only horizontal lines are desired, set +# the vertical (y) spacing larger than the height of the image. The default=8. +# Note: if you want the bottom and/or right grid line to show, then the image +# dimension(s) must be a multiple of the grid spacing plus 1. +# +# -c color ... COLOR is the color of the grid lines. Any valid IM color +# specification is allowed. Be sure to color values in double quotes. +# The default="black". +# +# -t thickness ... THICKNESS is the grid line thickness. Values are positive +# integers. The default=1 +# +# -o opacity ... OPACITY is the grid line opacity. Values are non-negative +# floats between 0.0 and 1.0. The default=1 +# +# CAVEAT: No guarantee that this script will work on all platforms, +# nor that trapping of inconsistent parameters is complete and +# foolproof. Use At Your Own Risk. +# +###### +# + +# set default values +spacing=16 +color="black" +thickness=1 +opacity=1 + + +# set directory for temporary files +dir="." # suggestions are dir="." or dir="/tmp" + + +# set up functions to report Usage and Usage with Description +PROGNAME=`type $0 | awk '{print $3}'` # search for executable on path +PROGDIR=`dirname $PROGNAME` # extract directory of program +PROGNAME=`basename $PROGNAME` # base name of program +usage1() + { + echo >&2 "" + echo >&2 "$PROGNAME:" "$@" + sed >&2 -n '/^###/q; /^#/!q; s/^#//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" + } +usage2() + { + echo >&2 "" + echo >&2 "$PROGNAME:" "$@" + sed >&2 -n '/^######/q; /^#/!q; s/^#*//; s/^ //; 4,$p' "$PROGDIR/$PROGNAME" + } + + +# function to report error messages +errMsg() + { + echo "" + echo $1 + echo "" + usage1 + exit 1 + } + + +# function to test for minus at start of value of second part of option 1 or 2 +checkMinus() + { + test=`echo "$1" | grep -c '^-.*$'` # returns 1 if match; 0 otherwise + [ $test -eq 1 ] && errMsg "$errorMsg" + } + +# test for correct number of arguments and get values +if [ $# -eq 0 ] + then + # help information + echo "" + usage2 + exit 0 +elif [ $# -gt 10 ] + then + errMsg "--- TOO MANY ARGUMENTS WERE PROVIDED ---" +else + while [ $# -gt 0 ] + do + # get parameter values + case "$1" in + -h|-help) # help information + echo "" + usage2 + exit 0 + ;; + -s) # get spacing + shift # to get the next parameter - spacing + # test if parameter starts with minus sign + errorMsg="--- INVALID SPACING SPECIFICATION ---" + checkMinus "$1" + spacing="$1," + ;; + -c) # get color + shift # to get the next parameter - lineval + # test if parameter starts with minus sign + errorMsg="--- INVALID COLOR SPECIFICATION ---" + checkMinus "$1" + # test lineval values + color="$1" + ;; + -t) # get thickness + shift # to get the next parameter - thickness + # test if parameter starts with minus sign + errorMsg="--- INVALID THICKNESS SPECIFICATION ---" + checkMinus "$1" + # test width values + thickness=`expr "$1" : '\([0-9]*\)'` + [ "$thickness" = "" -o $thickness -eq 0 ] && errMsg "--- THICKNESS=$thickness MUST BE A POSITIVE INTEGER ---" + ;; + -o) # get opacity + shift # to get the next parameter - opacity + # test if parameter starts with minus sign + errorMsg="--- INVALID OPACITY SPECIFICATION ---" + checkMinus "$1" + # test width values + opacity=`expr "$1" : '\([.0-9]*\)'` + [ "$opacity" = "" ] && errMsg "OPACITY=$opacity IS NOT A NON-NEGATIVE FLOATING POINT NUMBER" + opacitytest=`echo "$opacity > 1" | bc` + [ $opacitytest -eq 1 ] && errMsg "OPACITY=$opacity MUST BE BETWEEN 0.0 AND 1.0" + ;; + -) # STDIN and end of arguments + break + ;; + -*) # any other - argument + errMsg "--- UNKNOWN OPTION ---" + ;; + *) # end of arguments + break + ;; + esac + shift # next option + done + # + # get infile and outfile + infile=$1 + outfile=$2 +fi + + +# test that infile provided +[ "$infile" = "" ] && errMsg "NO INPUT FILE SPECIFIED" + +# test that outfile provided +[ "$outfile" = "" ] && errMsg "NO OUTPUT FILE SPECIFIED" + +# setup temporary images and auto delete upon exit +# use mpc/cache to hold input image temporarily in memory +tmpA="$dir/profile_$$.mpc" +tmpB="$dir/profile_$$.cache" +trap "rm -f $tmpA $tmpB; exit 0" 0 +trap "rm -f $tmpA $tmpB; exit 1" 1 2 3 15 + + +# +if convert -quiet -regard-warnings "$infile" +repage "$tmpA" + then + : 'do nothing - continue processing below' + else + errMsg "--- FILE $infile DOES NOT EXIST OR IS NOT AN ORDINARY FILE, NOT READABLE OR HAS ZERO SIZE ---" +fi + + +# get image dimensions +width=`identify -format %w $tmpA` +height=`identify -format %h $tmpA` + +width1=`expr $width - 1` +height1=`expr $height - 1` + +xinc=`echo "$spacing" | cut -d, -f1` +yinc=`echo "$spacing" | cut -d, -f2` +[ "$yinc" = "" ] && yinc=$xinc +testx=`expr $xinc : '[0-9]*'` +testy=`expr $yinc : '[0-9]*'` +[ $testx -eq 0 -o $testy -eq 0 ] && errMsg "--- SPACING MUST BE AN INTEGER ---" + +# get string for drawing grid lines +drawstr="" + +if [ $yinc -le $height ] + then + i=0 + while [ $i -le $height ] + do + drawstr="$drawstr M 0,$i L $width1,$i" + i=`expr $i + $yinc` + done +fi + +if [ $xinc -le $width ] + then + i=0 + while [ $i -le $width ] + do + drawstr="$drawstr M $i,0 L $i,$height1" + i=`expr $i + $xinc` + done +fi + +# process image +convert $tmpA -fill none -stroke $color -strokewidth $thickness -draw "stroke-opacity $opacity path '$drawstr'" $outfile +exit 0 diff --git a/imbreak_main.js b/imbreak_main.js new file mode 100644 index 0000000..78ae753 --- /dev/null +++ b/imbreak_main.js @@ -0,0 +1,113 @@ +var Main = + { + firsttime: true, + generating: false, + thelast: "", + enter: function (e) + { + if (Main.generating) + return + if (e.keyCode === 13) + Main.go() + }, + go: function () + { + if (Main.generating) + return + Main.generating = true + var theloader = '<span style="width:100%;margin-right:40%"><img style="width:140px;height:120px;display:inline;" src="generating.gif"></img></span>' + $("#output-cmd").html(theloader).show() + $('.result').show() + $('.results').show() + $("#output-img").show() + $("#output-url").show() + $("#result").show() + var data = + { + breakmode:$('input:radio[name=modeswitch]:checked').val(), + breaktype: $('#breaktype :selected').val(), + breakangle: $("#breakangle").val(), + url: $('#url').val(), + username: $('#username').val(), + firsttime: Main.firsttime.toString() + } + if (data["breakmode"] == "gradual") + { + data["breakmode"] = "subtle" + if (Main.lines && Main.thelast == $('#url').val()) + { + Main.firsttime = false + data["url"] = Main.lines[1] + } + } + else + { + Main.firsttime = true + } + Main.thelast = $('#url').val(); + thestring = JSON.stringify(data); + $('#error').append(thestring); + if (data.username.length > 0) + document.cookie = "imname="+data.username+";path=/;domain=.asdf.us;max-age=1086400" + $.post("/cgi-bin/im/break/breaker", data, Main.callback) + }, + error: function (s) + { + $("#output-cmd").html("<span class='error'>ERROR: " + s + "</span>").show() + $("#output-url").hide() + $("#output-img").hide() + }, + filesize: function (size) + { + if (size < 1024) + return size.toString() + " bytes" + if (size < 1024 * 1024) + return Math.floor (size/1024).toString() + " KB" + else + return Math.floor (size/(1024*1024)).toString() + " MB" + }, + callback: function (data) + { + data = JSON.parse(data) + $('#error').append('called'); + $("#output-cmd").html('') + $('#output-url').val(data.url) + $("#output-img").html("click image to enlarge<br><a target=_blank href='"+data.url+"'>"+"<img src='"+data.url+"' id='output-image'></img><br>"+"</a>" +); + $("#output-info").html('-ACTUAL SIZE-<br>'+Main.filesize(data.size)+'<br>'+data.width+'<br>'+data.height+'<br><br>'+'<span>see more at →<a href="http://asdf.us/im/gallery">photoblaster gallery</a></span>'+'<br>') + Main.generating = false + }, + cookie: function () + { + if (document.cookie) + { + var cookies = document.cookie.split(";") + for (i in cookies) + { + var cookie = cookies[i].split("=") + if (cookie[0].indexOf("imname") !== -1) + { + if (cookie[1] !== 'false' && cookie[1] !== 'undefined' && cookie[1].length) + { + return cookie[1] + } + } + } + } + return "" + }, + init: function () + { + var name = Main.cookie () + $("#username").val(name) + $("#breakbutton").bind("click", Main.go) + $(document).bind("keydown", Main.enter) + } + } +//$('#theform').each(function(){ +// this.reset(); +// }); + + + +Main.init () diff --git a/imgradient_index.html b/imgradient_index.html new file mode 100644 index 0000000..70a39dc --- /dev/null +++ b/imgradient_index.html @@ -0,0 +1,532 @@ +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml"> +<!-- + __ __ __ __ __ __ __ +/_/\/\ /_/\/\ /_/\/\ /_/\/\ /_/\/\ /_/\/\ /_/\/\ +\_\ / \_\ / \_\ / \_\ / \_\ / \_\ / \_\ / +/_/ \ /_/ \ /_/ \ /_/ \ /_/ \ /_/ \ /_/ \ +\_\/\ \ \_\/\ \ \_\/\ \ \_\/\ \ \_\/\ \ \_\/\ \ \_\/\ \ + \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ \_\/ +--> + +<html> +<head> +<title>GRADIENT PHOTOBLASTER</title> +<meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> +<meta property="og:title" content="GRADIENT PHOTOBLASTER"/> +<meta property="og:type" content="website"/> +<meta name=Author content="Pepper .... with help from Jules Welter/LaPlace"/> +<meta property="og:url" content="http://asdf.us/imgradient/"/> +<meta property="og:image" content="http://asdf.us/g/experiment/whitegreencrazy.jpg" /> +<meta property="og:site_name" content="asdf.us"/> +<meta property="og:description" content="choose colors and options, let it flow out of you."/> +<meta property="fb:admins" content="100001923956223,1709246"/> +<link href="css/custom-theme/jquery-ui-1.8.16.custom.css" rel="stylesheet" type="text/css" /> +<style type="text/css"> +* + { + padding: 0; + margin: 0; + font-family: times; + } +body + { + background-color: #e6e0e0; + color: #696969; + overflow-x:hidden; + } +h1 + { + font-size: 40px; + } +#brightness-slider,#hue-slider,#saturation-slider,#blur-slider + { + margin-top:10px; + margin-bottom:10px; + background-color:#B3B3B3; + } +#stripenumber,#stripeintensity,#percentbeveled + { + height:10px; + width :25px; + font-size:9; + } +#controls,#result + { + padding: 10px; + width: 460px; + } +#controls + { + display:block; + font-size: 14px; + top: 10px; left: 10px; + z-index: 5; + background-color: #fff; + -moz-box-shadow: 0px 0px 9px 3px mediumseagreen; + -webkit-box-shadow: 0px 0px 9px 3px mediumseagreen; + box-shadow: 0px 0px 9px 3px mediumseagreen; + border: 60px outset #777; + } +#result + { + background-color: #d6d0d0; + top: 10px; left: 580px; + z-index: 5; + display: none; + position: fixed; + } +#instructions + { + position: absolute; + bottom: 10px; + left: 10px; + line-height: 18px; + z-index: 1; + } +p + { + background-color: #fff; + padding: 10px; + font-size: 14px; + width: 430px; + display: block; + } +#gallery-link + { + background-color: #fff; + position: absolute; + top: 10px; + right: 10px; + padding: 10px; + z-index: 10; + } +.error + { + color: red; + font-size: 20px; + } +a + { + color: #b4d; + font-weight: bold; + } +label + + { + display: inline-block; + width: 300px; + padding-right: 10px; + text-align: center; + } +input[type=text] + { + width: 100px; + } +#img-url,#output-url,#img-background + { + width: 200px; + } +#img-width,#img-height,#img-brightness,#img-saturation,#img-contrast,#img-hue,#img-rotate,#img-blur,#img-tilt + { + width: 50px; + text-align: right; + } +button + { + padding: 2px 5px; + font-size: 16px; + width:200px; + float:right; + font-weight:bold; + color:#222222; + } +#result img + { + max-width: 400px; + max-height: 400px; + } +#output-cmd + { + font-size: 12px; + white-space: pre; + } +.shim + { + height: 10px; + clear: both; + display: block; + } +#output-url + { + width: 320px; + } +#likebutton + { + position: fixed; + bottom: 10px; + right: 10px; + width: 350px; + background-color: #fff; + padding: 10px; + color: #ddd; + font-family: times; + text-transform: uppercase; + } +.arrow + { + float:right; + white-space:inherit; + display:inline; + } + +</style> +<body> +<div id="error"></div> +<div id="controls"> +<form id="reset" onsubmit="return false"> +<h1> + GRADIENT GENERATOR FOR PHOTOBLASTER + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115918_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115933_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115941_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115946_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115946_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115946_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115946_pepper.gif"/> + <img src="http://i.asdf.us/im/4e/angreekcolumnshowcaselg_1315115946_pepper.gif"/> +</h1> + + <span class="shim"></span> + + + <label><span style="font-weight:bold; color:black;">CHOOSE A GRADIENT TYPE<span class="arrow">→</span></span></label> + <select id="gradient-type"> + <option selected="selected" value="gradient">STRAIGHT FADE</option> + <option value="plasma">PLASMA</option> + <!-- <option value="-function sinusoid 4,-90 gradient:">bands</option>--> + <option value="canvas">JUST A FLAT COLOR</option> + <option value="radial">RADIAL GRADIENT</option> + <option value="colorspace">GRAYSCALE PLASMA</option> + <option value="plasmawash">PLASMA WASH</option> + <option value="gradientwash">GRADIENT WASH</option> + <option value="mirrored">MIRRORED PLASMA</option> + <option value="noise">RANDOM NOISE</option> + </select> + + <span class="shim"></span> + + <label><span style="font-weight:bold">ADJUST THE WIDTH</span><span class="arrow">→</span></label> + <input type="text" id="img-width" value="400" /><small>px</small> + <br/> + + <span class="shim"></span> + + <label><span style="font-weight:bold">ADJUST THE HEIGHT</span><span class="arrow">→</span></label> + <input type="text" id="img-height" value="400" /><small>px</small> + <br/> + + <span class="shim"></span> + + <label>CHOOSE SOME COLORS<span class="arrow">→</span></label> + <a href="http://asdf.us/imgrid/colors" target="_blank">list of color names</a> + + <span class="shim"></span> + + <label><span style="font-weight:bold; color:black;">COLOR 1</span><span class="arrow">→</span></label> + <input type="text" id="img-color1" value="white" /> + <br/> + + <span class="shim"></span> + + <label><span style="font-weight:bold; color:black;">COLOR 2</span><span class="arrow">→</span></label> + <input type="text" id="img-color2" value="black" /> + <br/> + + <span class="shim"></span> + + <span style="font-size:11px"> + <label>BANDS(also called stripes)?<span class="arrow">→</span></label> + <input type="checkbox" id="stripes" value="1" /> + <small>if yes...</small><label>NUMBER:</label><input value="" type="text" id="stripenumber"></input><small>0-400</small><label>INTENSITY:</label><input value="" type="text" id="stripeintensity"></input><small>0-2000</small> + </span> + + <span class="shim"></span> + <span class="shim"></span> + + <label>BLURRINESS <small>(0-20)</small><span class="arrow">→</span></label> + <input type="text" id="img-blur" value="" /> + <br/> + + <div class="slider" id="blur-slider"></div> + + <label>HUE <small>(0-200)</small><span class="arrow">→</span></label> + <input type="text" id="img-hue" value="" /> + <br/> + + <div class="slider" id="hue-slider"></div> + + <label>SATURATION <small>(0-200)</small><span class="arrow">→</span></label> + <input type="text" id="img-saturation" value="" /> + <br/> + + <div class="slider" id="saturation-slider"></div> + + <label>BRIGHTNESS <small>(0-200)</small><span class="arrow">→</span></label> + <input type="text" id="img-brightness" value="" /> + <br/> + + <div class="slider" id="brightness-slider"></div> + + <span class="shim"></span> + + + <label><span style="font-size:11px">CHOOSE A HALFTONE FILTER</span><span class="arrow">→</span></label> + <select id="halftone-type"> + <option selected="selected" value="">None</option> + <option value="checkeredfade">checkered-fade</option> + <option value="etchedtransition">etched-transition</option> + <option value="bendaydots">benday dots</option> + <option value="smallerdots1">smaller dots 1</option> + <option value="smallerdots2">smaller dots 2</option> + <option value="flatstripes">flat stripes</option> + </select> + + + <span class="shim"></span> + + <label><span style="font-size:11px">ADD A BEVELED BORDER</span><span class="arrow">→</span></label> + + <select id="bevel-type"> + <option selected="selected" value="">None</option> + <option value="flatout">flat out</option> + <option value="flatinner">flat inner</option> + <option value="evenlyframed">evely framed</option> + <option value="biginner">big inner</option> + <option value="bigouter">big outer</option> + <option value="dramaticflatout">dramatic flat out</option> + <option value="dramaticflatinner">dramatic flat inner</option> + </select> + + <span style="font-size:11px"><label>PERCENT BEVELED?</label></span> <input val="" type="text" id="percentbeveled"><small>%</small> + + + <span class="shim"></span> + <span class="shim"></span> + <label>FLIP HORIZONTALLY? + <span class="arrow">→</span></label> + <input type="checkbox" id="img-flop" value="1" /> + <br/> + + <label>FLIP VERTICALLY?<span class="arrow">→</span></label> + <input type="checkbox" id="img-flip" value="1" /> + <br/> + <label>TILT<small>(0-360)</small><span class="arrow">→</span></label> + <input type="text" id="img-tilt" value="" />° + <br/> + <span class="shim"></span> + + <label>ROTATE THE CANVAS<small>(0-360)</small><span class="arrow">→</span></label> + <input type="text" id="img-rotate" value="" />° + <br/> + + <span class="shim"></span> + + <span class="shim"></span> + + <label>output format:</label> + <select id="img-format"> + <option selected="selected">png</option> + <option>jpg</option> + <option>gif</option> + </select> + + <span class="shim"></span> + + <label>PUT YOUR NAME HERE >>></label> + <input type="text" id="img-name" value="" /> + <br/> + + <span class="shim"></span> + + <label> </label> + <button id="img-generate">GENERATE</button> + + <span class="shim"></span> + <br> + VIEW AND ARRANGE THE PHOTOBLASTS → <a href="/im/gallery/" target="_blank">Image Gallery</a><br/> + <span class="shim"></span> + OPEN THE PHOTOBLASTER EDITOR → <a href="/im" target="_blank">PHOTOBLASTER</a> + <span class="shim"></span> + TOP PHOTOBLASTS GO TO THE TUMBLR → <a href="http://photoblaster.tumblr.com/">Photoblaster Tumblr</a> + </p> + +</div> + +<div id="result"> + → <input type="text" id="output-url"/><br/> + <span id="output-cmd"></span><br/> + + <img id="output-img" /> +</div> +</form> +</div> + +<div id="likebutton"> +<div id="fb-root" style="background-color: transparent;"></div><script src="http://connect.facebook.net/en_US/all.js#appId=236917449658413&xfbml=1"></script><fb:like href="http://asdf.us/im/" send="false" width="347" show_faces="true" colorscheme="light" font="" style="background-color: transparent;"></fb:like> +</div> + +<script type="text/javascript" src="/js/jquery.js"></script> +<script type="text/javascript" src="jquery-ui-1.8.16.custom.min.js"></script> +<script type="text/javascript"> + + $(function() { + $( "#blur-slider" ).slider({ + value:0, + min: 0, + max: 20, + step: 1, + slide: function( event, ui ) { + $( "#img-blur" ).val(ui.value); + } + }); + $( "#img-blur" ).val( $( "#blur-slider" ).slider( "value" ) ); + + $( "#brightness-slider" ).slider({ + value:100, + min: 0, + max: 200, + step: 1, + slide: function( event, ui ) { + $( "#img-brightness" ).val(ui.value); + } + }); + $( "#img-brightness" ).val( $( "#brightness-slider" ).slider( "value" ) ); + + $( "#hue-slider" ).slider({ + value:100, + min: 0, + max: 200, + step: 1, + slide: function( event, ui ) { + $( "#img-hue" ).val(ui.value); + } + }); + $( "#img-hue" ).val($( "#hue-slider" ).slider( "value" ) ); + + $( "#saturation-slider" ).slider({ + value:100, + min: 0, + max: 200, + step: 1, + slide: function( event, ui ) { + $( "#img-saturation" ).val(ui.value); + } + }); + $( "#img-saturation" ).val($( "#saturation-slider" ).slider( "value" ) ); + }); + +var Main = + { + API_HEADER: "#@imgradient", + enter: function (e) + { + if (e.keyCode === 13) + Main.go() + }, + go: function () + { + $("#output-cmd").html('generating...').show() + $("#result").show() + var data = + { + flip: $('#img-flip:checked').val() !== undefined ? "true" : "false", + flop: $('#img-flop:checked').val() !== undefined ? "true" : "false", + tilt: $('#img-tilt').val(), + rotate: $("#img-rotate").val(), + subtract: $("#img-subtract").val(), + width: $("#img-width").val(), + height: $("#img-height").val(), + color2: $("#img-color2").val(), + color1: $("#img-color1").val(), + brightness: $("#img-brightness").val(), + saturation: $("#img-saturation").val(), + blurriness: $("#img-blur").val(), + hue: $("#img-hue").val(), + contrast: $("#img-contrast").val(), + gradienttype: $('#gradient-type :selected').val(), + bevel: $('#bevel-type :selected').val(), + percentbeveled: $('#percentbeveled').val(), + halftone: $('#halftone-type :selected').val(), + stripes: $('#stripes:checked').val() !== undefined ? "true" : "false", + stripenumber: $('#stripenumber').val(), + stripeintensity: $('#stripeintensity').val(), + format: $('#img-format :selected').text(), + name: $("#img-name").val(), + } + if (data.name.length > 0) + document.cookie = "imname="+data.name+";path=/;domain=.asdf.us;max-age=1086400" + //FIXME + $.post("/cgi-bin/im/gradient", data, Main.callback) + $("#controls").css('margin',"") + }, + error: function (s) + { + $("#output-cmd").html("<span class='error'>ERROR: " + s + "</span>").show() + $("#output-url").hide() + $("#output-img").hide() + }, + callback: function (data) + { + data = JSON.parse(data) + if (data.error){ + return Main.error(data.error) + } + $("#output-cmd").html("size: "+Main.filesize(data.size)+"<br/>"+data.height + "x" + data.width) + $("#output-url").val(data.url) + $("#output-img").hide().attr("src", data.url).fadeIn(700) + }, + filesize: function (size) + { + if (size < 1024) + return size + " bytes" + if (size < 1024 * 1024) + return Math.floor (size/1024) + " KB" + else + return Math.floor (size/(1024*1024)) + " MB" + }, + cookie: function () + { + if (document.cookie) + { + var cookies = document.cookie.split(";") + for (i in cookies) + { + var cookie = cookies[i].split("=") + if (cookie[0].indexOf("imname") !== -1) + { + if (cookie[1] !== 'false' && cookie[1] !== 'undefined' && cookie[1].length) + { + return cookie[1] + } + } + } + } + return "" + }, + init: function () + { + var name = Main.cookie () + $("#img-name").val(name) + $("img-generate").click(function(){$('#img-generate').html('whos got a plastic bag')}) + $("#img-generate").bind("click", Main.go) + $(document).bind("keydown", Main.enter) + }, + } +Main.init () +document.getElementById("reset").reset() +</script> +<script type="text/javascript" src="http://asdf.us/js/pbembed.js"></script> +</body> +</html> + diff --git a/imgrid.py b/imgrid.py new file mode 100755 index 0000000..a751b6b --- /dev/null +++ b/imgrid.py @@ -0,0 +1,356 @@ +#!/usr/bin/python2.7 + +import sys +import re +import os +import string +import simplejson as json +import random +import time +import urllib +import urllib2 +from subprocess import Popen,PIPE,call +urlencode = urllib.urlencode +urlopen = urllib2.urlopen +Request = urllib2.Request + +MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 +WORKING_DIR = "/tmp" + +BIN_CONVERT = "/usr/bin/convert" +BIN_COMPOSITE = "/usr/bin/composite" +BIN_IDENTIFY = "/usr/bin/identify" +THREEDROTATE = "./3Drotate" +GRID = "./grid" +DB_TAG = "grid"; + +DEFAULT_HEIGHT = 400 +DEFAULT_WIDTH = 600 +DEFAULT_LINE_COLOR = "silver" +DEFAULT_FINALFORMAT = "png" + +#{{{Utility functions +def bool_correct(s): + if re.match(r'^false$', s, re.IGNORECASE): + return False + elif re.match(r'^true$', s, re.IGNORECASE): + return True + else: + return s + +class dotdict(dict): + """dot.notation access to dictionary attributes""" + def __getattr__(self, attr): + return self.get(attr) + __setattr__= dict.__setitem__ + __delattr__= dict.__delitem__ + +def get_mimetype(f): + try: + mimetype = Popen( + [BIN_IDENTIFY, f], stdout=PIPE + ).communicate()[0].split(" ")[1].lower() + return mimetype + except Exception as e: + sys.stderr.write(str(e)) + raise; + +def sanitize (str): + return re.sub(r'\W+', '', str) + +def now(): + return int(time.time()) + +def browser_request (url, data=None): + headers = { + 'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)', + 'Accept': '*/*', + } + try: + req = Request(url, data, headers) + response = urlopen(req) + except IOError, e: + if hasattr(e, 'code'): + sys.stderr.write( '%s - ERROR %s' % (url, e.code) ) + raise; + return None + else: + return response + +def download(url, destination, max_size=MAX_SIZE): + response = browser_request(url, None) + rawimg = response.read() + if len(rawimg) == 0: + sys.stderr.write("got zero-length file") + raise; + if len(rawimg) > max_size: + sys.stderr.write("file too big: max size {} KB / {} is {} KB".format( + str(MAX_SIZE/1024), + destination, + str(len(rawimg)/1024) + )) + raise; + f = open(destination, "w") + f.write(rawimg) + f.close() + +def file_size (filepath): + try: + return os.stat(file)[6] + except Exception as e: + sys.stderr.write(str(e)) + raise; + +def gif_frames(filepath): + try: + info = Popen([BIN_IDENTIFY,filepath], stdout=PIPE).communicate()[0] + frames = filter((lambda x: x), map( + (lambda x: x.split(" ")[0]), + (info).split('\n') + )) + return frames + except Exception as e: + sys.stderr.write(str(e)) + raise; +#}}} + +class Imgrid(): + def __init__(self, params): + self.params = {} + self.tag = "imGrid" + self.now = now() + self.files_created = [] + self.commands = []; + self.required_keys = [ +#{{{ required_keys + "width", + "height", + "linethickness", + "opacity", + "linecolor", + "spacing", + "vlines", + "hlines", + "shadow", + "bgimage", + "bgcolor", + "imageinstead", + "planebgcolor", + "planebgimage", + "swing", + "tilt", + "roll", + "zoom", + "skycolor", + "transition", + "trim", + "format", + "username" +#}}} + ] + for k in self.required_keys: + if k in params: + if k in [ 'bgimage', 'planebgimage', 'imageinstead' ] and bool_correct(params[k]): + self.params[k] = {} + self.params[k]['url'] = params[k] + self.params[k]['filename'] = "IMGRIDTMP{}_{}".format(now(), k) + + self.params[k]['path'] = os.path.join(WORKING_DIR, self.params[k]['filename']) + try: + download(self.params[k]['url'], self.params[k]['path']) + self.files_created.append(self.params[k]['path']) + self.params[k]['mimetype'] = get_mimetype(self.params[k]['path']) + frames = gif_frames(self.params[k]['path']) + if len(frames) > 1: + self.params[k]['path'] = random.choice(frames) + + + except Exception as e: + sys.stderr.write(str(e)) + raise; + else: + self.params[k] = bool_correct(sanitize(params[k])) + else: + self.params[k] = False; + + self.params = dotdict(self.params) + + self.basename = self._get_filename(); + + if not self.params.finalformat: + self.params.finalformat = DEFAULT_FINALFORMAT + self.filename = "{}.{}".format(self.basename, self.params.finalformat) + #final filepath is stored in self.filepath + self.filepath = os.path.join(WORKING_DIR, self.filename) + + def _get_filename(self): + return "{}_{}_{}".format( + self.tag, + now(), + self.params.username or "" + ); + + #makes a canvas file...step 1 (if not bgimage) + def _make_canvas(self): + dimensions = "{}x{}".format( + self.params.width or DEFAULT_WIDTH, + self.params.height or DEFAULT_HEIGHT + ) + if self.params.bgimage: + return + bgcolor = "xc:{}".format(self.params.bgcolor or 'transparent') + cmd = [ BIN_CONVERT, "-size", dimensions, bgcolor, self.filepath ] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + #2nd step-- run grid + def _grid_command(self): + cmd = [GRID] + if self.params.spacing: + if self.params.vlines: + width = 2 * int(self.params.width or DEFAULT_WIDTH) + cmd += ["-s","{},{}".format(self.params.spacing,width)] + elif self.params.hlines: + height = 2 * int(self.params.height or DEFAULT_HEIGHT) + cmd += ["-s", "{},{}".format(height,self.params.spacing)] + else: + cmd += ["-s",self.params.spacing] + cmd += [ "-c", self.params.linecolor or DEFAULT_LINE_COLOR] + if self.params.linethickness: cmd += ['-t',self.params.linethickness] + if self.params.opacity: cmd += ['-o',self.params.opacity] + cmd += [ self.filepath, self.filepath ] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + def _shadow_cmd(self): + #convert 1.png \( +clone -background black -shadow 110x1+9+9 \) +swap -background none -layers merge +repage 2.png + cmd = [ + BIN_CONVERT, + self.filepath, + "(","+clone","-background","black","-shadow","100x2+20+10",")", + "+swap","-background","none","-layers","merge","+repage" , + self.filepath + ] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise; + + + def _threed_rotate_cmd (self): + #3rd step--run 3Drotate + cmd = [THREEDROTATE] + if self.params.swing: cmd += ["pan={}".format(self.params.swing)] + if self.params.tilt: cmd += ["tilt={}".format(self.params.tilt)] + if self.params.roll: cmd += ["roll={}".format(self.params.roll)] + if self.params.zoom: cmd += ["zoom={}".format(self.params.zoom)] + if cmd == [THREEDROTATE]: #if nothing has been added + return + if self.params.planebgcolor and not self.params.planebgimage: + cmd += ["bgcolor={}".format(self.params.planebgcolor)] + else: + cmd += ["bgcolor=none"] + cmd += ["skycolor={}".format(self.params.skycolor or 'none')] + if self.params.transition: cmd += ["vp={}",self.params.transition] + cmd += [ self.filepath, self.filepath ] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise + + def _trim_cmd (self): + if self.params.trim: + cmd = [BIN_CONVERT, self.filepath, "-trim", "+repage", self.filepath] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise + + def _prepare_gridimage(self, image): + if image['mimetype'] != 'png': + cmd = [BIN_CONVERT, image['path'], self.filepath] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise + else: + cmd = ['cp', image['path'], self.filepath] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise + + def _overlay_planebgimage(self): + cmd = [ + BIN_COMPOSITE, + "-compose", "Dst_Over", "-gravity", "center", + self.params.planebgimage["path"], + self.filepath, + self.filepath + ] + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise + + def _cleanup(self): + if not len(self.files_created): + pass + cmd = ["rm", "-f"] + self.files_created + try: + call(cmd) + self.commands.append(" ".join(cmd)) + except Exception as e: + sys.stderr.write(str(e)) + raise + + def create(self): + if self.params.bgimage: + self._prepare_gridimage(self.params.bgimage) + self._grid_command() + elif self.params.imageinstead: + self._prepare_gridimage(self.params.imageinstead) + else: + self._make_canvas() + self._grid_command() + if self.params.shadow: self._shadow_cmd() + self._threed_rotate_cmd() + if self.params.planebgimage: self._overlay_planebgimage() + if self.params.trim: self._trim_cmd() + self._cleanup() + +if __name__ == "__main__": + g = Imgrid({ + 'bgimage' : 'http://i.asdf.us/im/1a/imBreak_1424909483_xx_abridged___.gif', + 'planebgimage' : 'http://i.imgur.com/FICZtph.png', + 'tilt' : '30', + 'spacing' : '30', + 'hlines' : 'true', + 'roll' : '30', + 'shadow' : 'true', + 'trim' : 'true' + }) + g.create() + print g.now + print g.filepath + print g.commands diff --git a/imgrid_main.js b/imgrid_main.js new file mode 100644 index 0000000..29114f2 --- /dev/null +++ b/imgrid_main.js @@ -0,0 +1,121 @@ +var Main = + { + API_HEADER: "#@imgrid", + generating: false, + enter: function (e) + { + if (Main.generating) + return + if (e.keyCode === 13) + Main.go() + }, + go: function () + { + if (Main.generating) + return + Main.generating = true + var theloader = '<span style="width:100%;margin-right:40%"><img style="width:140px;height:120px;display:inline;" src="generating.gif"></img></span>' + $("#output-cmd").html(theloader).show() + if($('#transition :selected').val() === 'tile'||$('#transition :selected').val()=== 'random') + { + $('#output-cmd').append("<br><span style='color:red'>WARNING: THIS REQUEST MIGHT TAKE A WHILE</span>") + } + $('.results').show() + $("#output-img").show() + $("#output-url").show() + $("#result").show() + var data = + { + width: $("#img-width").val(), + height: $("#img-height").val(), + linethickness: $("#line-thickness").val(), + opacity: $("#line-opacity").val(), + linecolor: $("#line-color").val(), + spacing: $("#line-spacing").val(), + vlines: $('#v-lines:checked').val() !== undefined ? "true" : "false", + hlines: $('#h-lines:checked').val() !== undefined ? "true" : "false", + shadow: $('#shadow:checked').val() !== undefined ? "true" : "false", + bgimage: $("#bg-image").val(), + bgcolor: $("#bg-color").val(), + imageinstead: $("#imageinstead").val(), + planebgcolor: $("#planebgcolor").val(), + skycolor: $("#skycolor").val(), + planebgimage: $("#planebgimage").val(), + transition: $('#transition :selected').val(), + swing: $("#swing").val(), + tilt: $("#tilt").val(), + roll: $("#roll").val(), + zoom: $("#zoom").val(), + trim: $("#trim:checked").val() !== undefined ? "true" : "false", + format: $('#format :selected').val(), + username: $('#username').val() + } + if (data.transition == 'infinite'){ + $('#genbutton').append("<span style='color:red'>WARNING:This might take a while</span>")} + if (data.username.length > 0) + document.cookie = "imname="+data.username+";path=/;domain=.asdf.us;max-age=1086400" + $.post("localhost:8999/imgrid", data, Main.callback) + }, + error: function (s) + { + $("#output-cmd").html("<span class='error'>ERROR: " + s + "</span>").show() + $("#output-url").hide() + $("#output-img").hide() + }, + filesize: function (size) + { + if (size < 1024) + return size.toString() + " bytes" + if (size < 1024 * 1024) + return Math.floor (size/1024).toString() + " KB" + else + return Math.floor (size/(1024*1024)).toString() + " MB" + }, + callback: function (data) + { + data = JSON.parse(data) + $("#output-cmd").html('') + $("#output-img").html("<a target=_blank href='"+data.url+"'>"+"<img src='"+data.url+"'></img><br>"+"</a>"); + $("#output-url").val(data.url) + $("#output-info").html('-ACTUAL SIZE-<br>'+Main.filesize(data.size)+'<br>'+data.width+'<br>'+data.height+'<br><br>'+'<span style="float:right">see more at →<a href="http://asdf.us/im/gallery">photoblaster gallery</a></span>'+'<br>') + Main.generating = false + }, + cookie: function () + { + if (document.cookie) + { + var cookies = document.cookie.split(";") + for (i in cookies) + { + var cookie = cookies[i].split("=") + if (cookie[0].indexOf("imname") !== -1) + { + if (cookie[1] !== 'false' && cookie[1] !== 'undefined' && cookie[1].length) + { + return cookie[1] + } + } + } + } + return "" + }, + init: function () + { + var name = Main.cookie () + $("#username").val(name) + if (name) + { +// $("#userlink").show() + // $("#userlink a").attr("href", "/im/gallery/?name="+name).html(name+"'s photoblasts") + } + $("#generate").bind("click", Main.go) + $(document).bind("keydown", Main.enter) + } + } +$('#theform').each(function(){ + this.reset(); + }); + + + +Main.init () diff --git a/pbserver.py b/pbserver.py new file mode 100755 index 0000000..82198ab --- /dev/null +++ b/pbserver.py @@ -0,0 +1,182 @@ +#!/usr/bin/python2.7 +from bottle import route, run, post, request + +from gradient import Gradient +from imgrid import Imgrid +from breaker import Breaker +from s3config import AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, BUCKET_NAME + + +import os +import sys +import db +import s3 +import mimetypes +import sha +from subprocess import call, Popen, PIPE +import simplejson as json +BIN_IDENTIFY = "/usr/bin/identify" + +#try: +# DB = db.db () +#except Exception as e: +# sys.stderr.write("Could not connect to db:\n{}".format(e)) +# sys.exit(1); +BASE_URL = "http://i.asdf.us" + +def hashdir(filename): + return sha.new(filename).hexdigest()[:2] + +def file_size (filepath): + try: + return os.stat(file)[6] + except Exception as e: + sys.stderr.write(str(e)) + raise; + +def bin_identify (filepath): + ident = Popen([BIN_IDENTIFY, filepath], stdout=PIPE).communicate()[0] + partz = ident.split(" ") + width,height = partz[2].split("x") + return width, height + + +def cleanup(filepath): + try: + call(['rm', filepath]) + except Exception as e: + sys.stderr.write(str(e)) + raise + +def moveToS3(filename,objectname): + conn = s3.AWSAuthConnection(AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY) + filedata = open(filename, 'rb').read() + content_type = mimetypes.guess_type(filename)[0] + try: + conn.put(BUCKET_NAME, objectname, s3.S3Object(filedata), + { + 'x-amz-acl': 'public-read', + 'Content-Type': content_type or 'text/plain', + 'x-amz-storage-class': 'REDUCED_REDUNDANCY' + } + ); + except Exception as e: + sys.stderr.write(str(e)) + raise + +def insert_cmd (date, remote_addr, username, url, directory, oldfile, newfile, cmd, dataobj, tag): + try: + sql = "INSERT INTO im_cmd " + sql += "(date, remote_addr, name, url, dir, oldfile, newfile, cmd, dataobj, tag) " + sql += "VALUES(%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)" + #or "NULL" + args = (now(), remote_addr, username, url, directory, oldfile, newfile, cmd, dataobj, tag) + #args = (now(), os.environ['REMOTE_ADDR'], name, url, dir, oldfile, newfile, " ".join(cmd),dataobj) + DB.execute(sql, args) + except Exception as e: + sys.stderr.write(str(e)) + return + +@post('/gradient') +def gradient(): + try: + im = Gradient(request.forms) + im.create(); + directory = hashdir(im.filename) + dimensions = bin_identify(im.filepath) + objectname = "{}/{}".format(directory, im.filename) +# s3move(im.filepath, objectname) + return(im.filepath, objectname) +# return "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}".format( +# insert_cmd( +# im.now, +# request.environ.get('REMOTE_ADDR'), +# im.params['username'] or "NULL", +# "NULL", +# directory, +# "NULL", +# im.filename, +# ";".join(im.commands), +# json.dumps(im.params), +# im.tag, +# ) + return json.loads({ + 'url' : "{}/{}".format(BASE_URL, objectname) + 'size' : file_size(im.filepath), + 'width' : "{}px".format(dimensions[0]), + 'height' : "{}px".format(dimensions[1]), + }) + #URL, FILESIZE, WIDTH, HEIGHT to replace index.html + except Exception as e: + sys.stderr.write(str(e)) + raise; + +@post('/imgrid') +def imgrid(): + try: + im = Imgrid(request.forms) + im.create(); + directory = hashdir(im.filename) + dimensions = bin_identify(im.filepath) + objectname = "{}/{}".format(directory, im.filename) +# s3move(im.filepath, objectname) + return(im.filepath, objectname) +# return "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}".format( +# insert_cmd( +# im.now, +# request.environ.get('REMOTE_ADDR'), +# im.params['username'] or "NULL", +# im.params.imageinstead or im.params.bgimage or im.params.planebgimage or "NULL", +# directory, +# "NULL", +# im.filename, +# ";".join(im.commands), +# json.dumps(im.params), +# im.tag, +# ) + return json.loads({ + 'url' : "{}/{}".format(BASE_URL, objectname) + 'size' : file_size(im.filepath), + 'width' : "{}px".format(dimensions[0]), + 'height' : "{}px".format(dimensions[1]), + }) + #URL, FILESIZE, WIDTH, HEIGHT to replace main.js + except Exception as e: + sys.stderr.write(str(e)) + raise; + +@post('/breaker') +def breaker(): + try: + im = Breaker(request.forms) + im.create(); + directory = hashdir(im.filename) + dimensions = bin_identify(im.filepath) + objectname = "{}/{}".format(directory, im.filename) +# s3move(im.filepath, objectname) + return(im.filepath, objectname) +# return "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}".format( +# insert_cmd( +# im.now, +# request.environ.get('REMOTE_ADDR'), +# im.params['username'] or "NULL", +# im.params['url'], +# directory, +# "NULL", +# im.filename, +# ";".join(im.commands), +# json.dumps(im.params), +# im.tag, +# ) + return json.loads({ + 'url' : "{}/{}".format(BASE_URL, objectname) + 'size' : file_size(im.filepath), + 'width' : "{}px".format(dimensions[0]), + 'height' : "{}px".format(dimensions[1]), + }) + #URL, FILESIZE, WIDTH, HEIGHT to replace main.js make sure you use px + except Exception as e: + sys.stderr.write(str(e)) + raise; + +run(host='0.0.0.0', port=8999, debug=True) @@ -0,0 +1,618 @@ +#!/usr/bin/env python + +# This software code is made available "AS IS" without warranties of any +# kind. You may copy, display, modify and redistribute the software +# code either by itself or as incorporated into your code; provided that +# you do not remove any proprietary notices. Your use of this software +# code is at your own risk and you waive any claim against Amazon +# Digital Services, Inc. or its affiliates with respect to your use of +# this software code. (c) 2006-2007 Amazon Digital Services, Inc. or its +# affiliates. + +import base64 +import hmac +import httplib +import re +import sha +import sys +import time +import urllib +import urlparse +import xml.sax + +DEFAULT_HOST = 's3.amazonaws.com' +PORTS_BY_SECURITY = { True: 443, False: 80 } +METADATA_PREFIX = 'x-amz-meta-' +AMAZON_HEADER_PREFIX = 'x-amz-' + +# generates the aws canonical string for the given parameters +def canonical_string(method, bucket="", key="", query_args={}, headers={}, expires=None): + interesting_headers = {} + for header_key in headers: + lk = header_key.lower() + if lk in ['content-md5', 'content-type', 'date'] or lk.startswith(AMAZON_HEADER_PREFIX): + interesting_headers[lk] = headers[header_key].strip() + + # these keys get empty strings if they don't exist + if not interesting_headers.has_key('content-type'): + interesting_headers['content-type'] = '' + if not interesting_headers.has_key('content-md5'): + interesting_headers['content-md5'] = '' + + # just in case someone used this. it's not necessary in this lib. + if interesting_headers.has_key('x-amz-date'): + interesting_headers['date'] = '' + + # if you're using expires for query string auth, then it trumps date + # (and x-amz-date) + if expires: + interesting_headers['date'] = str(expires) + + sorted_header_keys = interesting_headers.keys() + sorted_header_keys.sort() + + buf = "%s\n" % method + for header_key in sorted_header_keys: + if header_key.startswith(AMAZON_HEADER_PREFIX): + buf += "%s:%s\n" % (header_key, interesting_headers[header_key]) + else: + buf += "%s\n" % interesting_headers[header_key] + + # append the bucket if it exists + if bucket != "": + buf += "/%s" % bucket + + # add the key. even if it doesn't exist, add the slash + buf += "/%s" % urllib.quote_plus(key) + + # handle special query string arguments + + if query_args.has_key("acl"): + buf += "?acl" + elif query_args.has_key("torrent"): + buf += "?torrent" + elif query_args.has_key("logging"): + buf += "?logging" + elif query_args.has_key("location"): + buf += "?location" + + return buf + +# computes the base64'ed hmac-sha hash of the canonical string and the secret +# access key, optionally urlencoding the result +def encode(aws_secret_access_key, str, urlencode=False): + b64_hmac = base64.encodestring(hmac.new(aws_secret_access_key, str, sha).digest()).strip() + if urlencode: + return urllib.quote_plus(b64_hmac) + else: + return b64_hmac + +def merge_meta(headers, metadata): + final_headers = headers.copy() + for k in metadata.keys(): + final_headers[METADATA_PREFIX + k] = metadata[k] + + return final_headers + +# builds the query arg string +def query_args_hash_to_string(query_args): + query_string = "" + pairs = [] + for k, v in query_args.items(): + piece = k + if v != None: + piece += "=%s" % urllib.quote_plus(str(v)) + pairs.append(piece) + + return '&'.join(pairs) + + +class CallingFormat: + PATH = 1 + SUBDOMAIN = 2 + VANITY = 3 + + def build_url_base(protocol, server, port, bucket, calling_format): + url_base = '%s://' % protocol + + if bucket == '': + url_base += server + elif calling_format == CallingFormat.SUBDOMAIN: + url_base += "%s.%s" % (bucket, server) + elif calling_format == CallingFormat.VANITY: + url_base += bucket + else: + url_base += server + + url_base += ":%s" % port + + if (bucket != '') and (calling_format == CallingFormat.PATH): + url_base += "/%s" % bucket + + return url_base + + build_url_base = staticmethod(build_url_base) + + + +class Location: + DEFAULT = None + EU = 'EU' + + + +class AWSAuthConnection: + def __init__(self, aws_access_key_id, aws_secret_access_key, is_secure=True, + server=DEFAULT_HOST, port=None, calling_format=CallingFormat.SUBDOMAIN): + + if not port: + port = PORTS_BY_SECURITY[is_secure] + + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + self.is_secure = is_secure + self.server = server + self.port = port + self.calling_format = calling_format + + def create_bucket(self, bucket, headers={}): + return Response(self._make_request('PUT', bucket, '', {}, headers)) + + def create_located_bucket(self, bucket, location=Location.DEFAULT, headers={}): + if location == Location.DEFAULT: + body = "" + else: + body = "<CreateBucketConstraint><LocationConstraint>" + \ + location + \ + "</LocationConstraint></CreateBucketConstraint>" + return Response(self._make_request('PUT', bucket, '', {}, headers, body)) + + def check_bucket_exists(self, bucket): + return self._make_request('HEAD', bucket, '', {}, {}) + + def list_bucket(self, bucket, options={}, headers={}): + return ListBucketResponse(self._make_request('GET', bucket, '', options, headers)) + + def delete_bucket(self, bucket, headers={}): + return Response(self._make_request('DELETE', bucket, '', {}, headers)) + + def put(self, bucket, key, object, headers={}): + if not isinstance(object, S3Object): + object = S3Object(object) + + return Response( + self._make_request( + 'PUT', + bucket, + key, + {}, + headers, + object.data, + object.metadata)) + + def get(self, bucket, key, headers={}): + return GetResponse( + self._make_request('GET', bucket, key, {}, headers)) + + def delete(self, bucket, key, headers={}): + return Response( + self._make_request('DELETE', bucket, key, {}, headers)) + + def get_bucket_logging(self, bucket, headers={}): + return GetResponse(self._make_request('GET', bucket, '', { 'logging': None }, headers)) + + def put_bucket_logging(self, bucket, logging_xml_doc, headers={}): + return Response(self._make_request('PUT', bucket, '', { 'logging': None }, headers, logging_xml_doc)) + + def get_bucket_acl(self, bucket, headers={}): + return self.get_acl(bucket, '', headers) + + def get_acl(self, bucket, key, headers={}): + return GetResponse( + self._make_request('GET', bucket, key, { 'acl': None }, headers)) + + def put_bucket_acl(self, bucket, acl_xml_document, headers={}): + return self.put_acl(bucket, '', acl_xml_document, headers) + + def put_acl(self, bucket, key, acl_xml_document, headers={}): + return Response( + self._make_request( + 'PUT', + bucket, + key, + { 'acl': None }, + headers, + acl_xml_document)) + + def list_all_my_buckets(self, headers={}): + return ListAllMyBucketsResponse(self._make_request('GET', '', '', {}, headers)) + + def get_bucket_location(self, bucket): + return LocationResponse(self._make_request('GET', bucket, '', {'location' : None})) + + # end public methods + + def _make_request(self, method, bucket='', key='', query_args={}, headers={}, data='', metadata={}): + + server = '' + if bucket == '': + server = self.server + elif self.calling_format == CallingFormat.SUBDOMAIN: + server = "%s.%s" % (bucket, self.server) + elif self.calling_format == CallingFormat.VANITY: + server = bucket + else: + server = self.server + + path = '' + + if (bucket != '') and (self.calling_format == CallingFormat.PATH): + path += "/%s" % bucket + + # add the slash after the bucket regardless + # the key will be appended if it is non-empty + path += "/%s" % urllib.quote_plus(key) + + + # build the path_argument string + # add the ? in all cases since + # signature and credentials follow path args + if len(query_args): + path += "?" + query_args_hash_to_string(query_args) + + is_secure = self.is_secure + host = "%s:%d" % (server, self.port) + while True: + if (is_secure): + connection = httplib.HTTPSConnection(host) + else: + connection = httplib.HTTPConnection(host) + + final_headers = merge_meta(headers, metadata); + # add auth header + self._add_aws_auth_header(final_headers, method, bucket, key, query_args) + + connection.request(method, path, data, final_headers) + resp = connection.getresponse() + if resp.status < 300 or resp.status >= 400: + return resp + # handle redirect + location = resp.getheader('location') + if not location: + return resp + # (close connection) + resp.read() + scheme, host, path, params, query, fragment \ + = urlparse.urlparse(location) + if scheme == "http": is_secure = True + elif scheme == "https": is_secure = False + else: raise invalidURL("Not http/https: " + location) + if query: path += "?" + query + # retry with redirect + + def _add_aws_auth_header(self, headers, method, bucket, key, query_args): + if not headers.has_key('Date'): + headers['Date'] = time.strftime("%a, %d %b %Y %X GMT", time.gmtime()) + + c_string = canonical_string(method, bucket, key, query_args, headers) + headers['Authorization'] = \ + "AWS %s:%s" % (self.aws_access_key_id, encode(self.aws_secret_access_key, c_string)) + + +class QueryStringAuthGenerator: + # by default, expire in 1 minute + DEFAULT_EXPIRES_IN = 60 + + def __init__(self, aws_access_key_id, aws_secret_access_key, is_secure=True, + server=DEFAULT_HOST, port=None, calling_format=CallingFormat.SUBDOMAIN): + + if not port: + port = PORTS_BY_SECURITY[is_secure] + + self.aws_access_key_id = aws_access_key_id + self.aws_secret_access_key = aws_secret_access_key + if (is_secure): + self.protocol = 'https' + else: + self.protocol = 'http' + + self.is_secure = is_secure + self.server = server + self.port = port + self.calling_format = calling_format + self.__expires_in = QueryStringAuthGenerator.DEFAULT_EXPIRES_IN + self.__expires = None + + # for backwards compatibility with older versions + self.server_name = "%s:%s" % (self.server, self.port) + + def set_expires_in(self, expires_in): + self.__expires_in = expires_in + self.__expires = None + + def set_expires(self, expires): + self.__expires = expires + self.__expires_in = None + + def create_bucket(self, bucket, headers={}): + return self.generate_url('PUT', bucket, '', {}, headers) + + def list_bucket(self, bucket, options={}, headers={}): + return self.generate_url('GET', bucket, '', options, headers) + + def delete_bucket(self, bucket, headers={}): + return self.generate_url('DELETE', bucket, '', {}, headers) + + def put(self, bucket, key, object, headers={}): + if not isinstance(object, S3Object): + object = S3Object(object) + + return self.generate_url( + 'PUT', + bucket, + key, + {}, + merge_meta(headers, object.metadata)) + + def get(self, bucket, key, headers={}): + return self.generate_url('GET', bucket, key, {}, headers) + + def delete(self, bucket, key, headers={}): + return self.generate_url('DELETE', bucket, key, {}, headers) + + def get_bucket_logging(self, bucket, headers={}): + return self.generate_url('GET', bucket, '', { 'logging': None }, headers) + + def put_bucket_logging(self, bucket, logging_xml_doc, headers={}): + return self.generate_url('PUT', bucket, '', { 'logging': None }, headers) + + def get_bucket_acl(self, bucket, headers={}): + return self.get_acl(bucket, '', headers) + + def get_acl(self, bucket, key='', headers={}): + return self.generate_url('GET', bucket, key, { 'acl': None }, headers) + + def put_bucket_acl(self, bucket, acl_xml_document, headers={}): + return self.put_acl(bucket, '', acl_xml_document, headers) + + # don't really care what the doc is here. + def put_acl(self, bucket, key, acl_xml_document, headers={}): + return self.generate_url('PUT', bucket, key, { 'acl': None }, headers) + + def list_all_my_buckets(self, headers={}): + return self.generate_url('GET', '', '', {}, headers) + + def make_bare_url(self, bucket, key=''): + full_url = self.generate_url(self, bucket, key) + return full_url[:full_url.index('?')] + + def generate_url(self, method, bucket='', key='', query_args={}, headers={}): + expires = 0 + if self.__expires_in != None: + expires = int(time.time() + self.__expires_in) + elif self.__expires != None: + expires = int(self.__expires) + else: + raise "Invalid expires state" + + canonical_str = canonical_string(method, bucket, key, query_args, headers, expires) + encoded_canonical = encode(self.aws_secret_access_key, canonical_str) + + url = CallingFormat.build_url_base(self.protocol, self.server, self.port, bucket, self.calling_format) + + url += "/%s" % urllib.quote_plus(key) + + query_args['Signature'] = encoded_canonical + query_args['Expires'] = expires + query_args['AWSAccessKeyId'] = self.aws_access_key_id + + url += "?%s" % query_args_hash_to_string(query_args) + + return url + + +class S3Object: + def __init__(self, data, metadata={}): + self.data = data + self.metadata = metadata + +class Owner: + def __init__(self, id='', display_name=''): + self.id = id + self.display_name = display_name + +class ListEntry: + def __init__(self, key='', last_modified=None, etag='', size=0, storage_class='', owner=None): + self.key = key + self.last_modified = last_modified + self.etag = etag + self.size = size + self.storage_class = storage_class + self.owner = owner + +class CommonPrefixEntry: + def __init(self, prefix=''): + self.prefix = prefix + +class Bucket: + def __init__(self, name='', creation_date=''): + self.name = name + self.creation_date = creation_date + +class Response: + def __init__(self, http_response): + self.http_response = http_response + # you have to do this read, even if you don't expect a body. + # otherwise, the next request fails. + self.body = http_response.read() + if http_response.status >= 300 and self.body: + self.message = self.body + else: + self.message = "%03d %s" % (http_response.status, http_response.reason) + + + +class ListBucketResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + if http_response.status < 300: + handler = ListBucketHandler() + xml.sax.parseString(self.body, handler) + self.entries = handler.entries + self.common_prefixes = handler.common_prefixes + self.name = handler.name + self.marker = handler.marker + self.prefix = handler.prefix + self.is_truncated = handler.is_truncated + self.delimiter = handler.delimiter + self.max_keys = handler.max_keys + self.next_marker = handler.next_marker + else: + self.entries = [] + +class ListAllMyBucketsResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + if http_response.status < 300: + handler = ListAllMyBucketsHandler() + xml.sax.parseString(self.body, handler) + self.entries = handler.entries + else: + self.entries = [] + +class GetResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + response_headers = http_response.msg # older pythons don't have getheaders + metadata = self.get_aws_metadata(response_headers) + self.object = S3Object(self.body, metadata) + + def get_aws_metadata(self, headers): + metadata = {} + for hkey in headers.keys(): + if hkey.lower().startswith(METADATA_PREFIX): + metadata[hkey[len(METADATA_PREFIX):]] = headers[hkey] + del headers[hkey] + + return metadata + +class LocationResponse(Response): + def __init__(self, http_response): + Response.__init__(self, http_response) + if http_response.status < 300: + handler = LocationHandler() + xml.sax.parseString(self.body, handler) + self.location = handler.location + +class ListBucketHandler(xml.sax.ContentHandler): + def __init__(self): + self.entries = [] + self.curr_entry = None + self.curr_text = '' + self.common_prefixes = [] + self.curr_common_prefix = None + self.name = '' + self.marker = '' + self.prefix = '' + self.is_truncated = False + self.delimiter = '' + self.max_keys = 0 + self.next_marker = '' + self.is_echoed_prefix_set = False + + def startElement(self, name, attrs): + if name == 'Contents': + self.curr_entry = ListEntry() + elif name == 'Owner': + self.curr_entry.owner = Owner() + elif name == 'CommonPrefixes': + self.curr_common_prefix = CommonPrefixEntry() + + + def endElement(self, name): + if name == 'Contents': + self.entries.append(self.curr_entry) + elif name == 'CommonPrefixes': + self.common_prefixes.append(self.curr_common_prefix) + elif name == 'Key': + self.curr_entry.key = self.curr_text + elif name == 'LastModified': + self.curr_entry.last_modified = self.curr_text + elif name == 'ETag': + self.curr_entry.etag = self.curr_text + elif name == 'Size': + self.curr_entry.size = int(self.curr_text) + elif name == 'ID': + self.curr_entry.owner.id = self.curr_text + elif name == 'DisplayName': + self.curr_entry.owner.display_name = self.curr_text + elif name == 'StorageClass': + self.curr_entry.storage_class = self.curr_text + elif name == 'Name': + self.name = self.curr_text + elif name == 'Prefix' and self.is_echoed_prefix_set: + self.curr_common_prefix.prefix = self.curr_text + elif name == 'Prefix': + self.prefix = self.curr_text + self.is_echoed_prefix_set = True + elif name == 'Marker': + self.marker = self.curr_text + elif name == 'IsTruncated': + self.is_truncated = self.curr_text == 'true' + elif name == 'Delimiter': + self.delimiter = self.curr_text + elif name == 'MaxKeys': + self.max_keys = int(self.curr_text) + elif name == 'NextMarker': + self.next_marker = self.curr_text + + self.curr_text = '' + + def characters(self, content): + self.curr_text += content + + +class ListAllMyBucketsHandler(xml.sax.ContentHandler): + def __init__(self): + self.entries = [] + self.curr_entry = None + self.curr_text = '' + + def startElement(self, name, attrs): + if name == 'Bucket': + self.curr_entry = Bucket() + + def endElement(self, name): + if name == 'Name': + self.curr_entry.name = self.curr_text + elif name == 'CreationDate': + self.curr_entry.creation_date = self.curr_text + elif name == 'Bucket': + self.entries.append(self.curr_entry) + + def characters(self, content): + self.curr_text = content + + +class LocationHandler(xml.sax.ContentHandler): + def __init__(self): + self.location = None + self.state = 'init' + + def startElement(self, name, attrs): + if self.state == 'init': + if name == 'LocationConstraint': + self.state = 'tag_location' + self.location = '' + else: self.state = 'bad' + else: self.state = 'bad' + + def endElement(self, name): + if self.state == 'tag_location' and name == 'LocationConstraint': + self.state = 'done' + else: self.state = 'bad' + + def characters(self, content): + if self.state == 'tag_location': + self.location += content +
diff --git a/s3config.py b/s3config.py new file mode 100644 index 0000000..49357ad --- /dev/null +++ b/s3config.py @@ -0,0 +1,3 @@ +AWS_ACCESS_KEY_ID = 'AKIAIR53VPBXKJMXZIBA' +AWS_SECRET_ACCESS_KEY = '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' +BUCKET_NAME = 'i.asdf.us' @@ -0,0 +1,6 @@ +#!/bin/bash +curl --data 'width=200&height=500&stripes=true&stripenumber=50&stripeintensity=50&color1=green&color2=silver' localhost:8999/gradient +echo "" +curl --data 'width=200&height=500' localhost:8999/imgrid +echo "" +curl --data 'url=http://i.asdf.us/im/56/6005101large_1424913577_bike.gif&breaktype=RGB_WASH&breakmode=extreme' localhost:8999/breaker |
