From 03f45bb4225740f2ebe4adfd8de24f67d33ee0e3 Mon Sep 17 00:00:00 2001 From: yo mama Date: Thu, 30 Apr 2015 15:08:01 -0700 Subject: good --- 3Drotate | 873 -------------------------------------------------- README.md | 0 bevelborder | 295 ----------------- bin/3Drotate | 873 ++++++++++++++++++++++++++++++++++++++++++++++++++ bin/bevelborder | 295 +++++++++++++++++ bin/grid | 241 ++++++++++++++ breaker.py | 151 ++------- db.py | 31 -- gradient.py | 103 ++---- grid | 241 -------------- imgrid.py | 198 +++--------- install/pb_nginx.conf | 22 ++ lib/__init__.py | 0 lib/db.py | 31 ++ lib/utils.py | 122 +++++++ pattern.py | 151 ++------- pb_nginx.conf | 22 -- pbserver.py | 19 +- s3.py | 618 ----------------------------------- s3config.py | 3 - 20 files changed, 1723 insertions(+), 2566 deletions(-) delete mode 100755 3Drotate delete mode 100644 README.md delete mode 100755 bevelborder create mode 100755 bin/3Drotate create mode 100755 bin/bevelborder create mode 100755 bin/grid delete mode 100755 db.py delete mode 100755 grid create mode 100644 install/pb_nginx.conf create mode 100644 lib/__init__.py create mode 100755 lib/db.py create mode 100644 lib/utils.py delete mode 100644 pb_nginx.conf delete mode 100644 s3.py delete mode 100644 s3config.py diff --git a/3Drotate b/3Drotate deleted file mode 100755 index 227b92d..0000000 --- a/3Drotate +++ /dev/null @@ -1,873 +0,0 @@ -#!/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/README.md b/README.md deleted file mode 100644 index e69de29..0000000 diff --git a/bevelborder b/bevelborder deleted file mode 100755 index 6f6ec2a..0000000 --- a/bevelborder +++ /dev/null @@ -1,295 +0,0 @@ -#!/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/bin/3Drotate b/bin/3Drotate new file mode 100755 index 0000000..227b92d --- /dev/null +++ b/bin/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/bin/bevelborder b/bin/bevelborder new file mode 100755 index 0000000..6f6ec2a --- /dev/null +++ b/bin/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/bin/grid b/bin/grid new file mode 100755 index 0000000..58d0e08 --- /dev/null +++ b/bin/grid @@ -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/breaker.py b/breaker.py index 4bb5bf9..06c89cf 100755 --- a/breaker.py +++ b/breaker.py @@ -1,29 +1,18 @@ #!/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 +import lib.utils as utils +import urllib +from config import * -#WORKING_DIR = "/tmp" -WORKING_DIR = "/var/www/cache" -BIN_CONVERT = "/usr/bin/convert" -BIN_IDENTIFY = "/usr/bin/identify" DEFAULT_FINALFORMAT = "png"; - SUBTLE_BREAK_MARK = 'pron' EXTREME_BREAK_MARK = 'sugar' HEADER_OFFSET = 2000 -MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 - # 'CLASSIC':'jpg', # 'REDUX':'pcds', # 'BLURRY_BREAK':'viff', @@ -37,99 +26,10 @@ MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 # '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): + def __init__(self, **kwargs): self.params = {} - self.now = now() self.tag = "imBreak" self.commands = []; self._required_keys = [ @@ -143,31 +43,31 @@ class Breaker(): ] self.files_created = [] for k in self._required_keys: - if k in params: + if k in kwargs: if k == 'breaktype': - self.params['breaktype'] = self._get_breaktype(params[k]) + self.params['breaktype'] = self._get_breaktype(kwargs[k]) elif k == 'url': - self.params[k] = params[k] + self.params[k] = kwargs[k] else: - self.params[k] = bool_correct(sanitize(params[k])) + self.params[k] = utils.bool_correct(utils.sanitize(kwargs[k])) else: self.params[k] = False; - self.params = dotdict(self.params) + self.params = utils.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)) # same here try: - download(self.params.url, self._downloaded_file) + utils.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 = utils.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) # same here + self.width, self.height = utils.dimensions(self._downloaded_file) # same here if not self.params.finalformat: self.params.finalformat = DEFAULT_FINALFORMAT @@ -179,19 +79,15 @@ class Breaker(): #final filepath is stored in self.filepath self.filename = "{}.{}".format(self.basename, self.params.finalformat) self.filepath = os.path.join(WORKING_DIR, self.filename) - #lets go back to this in a second self._conversion_file = os.path.join(WORKING_DIR, "IMBREAKTMP{}.{}".format(self.basename, self.params.breaktype)) # this def _call_cmd(self, cmd, error=""): try: - call(cmd) + utils.call_cmd(cmd) self.commands.append(" ".join(cmd)); - except Exception as e: - if error: - sys.stderr.write(error) - else: - sys.stderr.write(str(e)) + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) def _get_breaktype(self, key): #{{{ conversion table @@ -224,8 +120,8 @@ class Breaker(): name_part = url.split("/")[-1] try: parts = name_part.split(".") - name_part = sanitize(parts[-2]) - file_format = sanitize(parts[-1]) + name_part = utils.sanitize(parts[-2]) + file_format = utils.sanitize(parts[-1]) if not name_part or not file_format: sys.stderr.write( "Incompatible input file type") raise; @@ -237,7 +133,7 @@ class Breaker(): 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 + return "{}{}_{}_{}".format(self.tag, name_part, utils.now(), self.params.username or ""), file_format #{{{#########rotatefunctions####################################### def _rotate(self): @@ -357,6 +253,15 @@ class Breaker(): self._cleanup() if __name__ == "__main__": - b = Breaker(TEST_PARAMS) + 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" + } + b = Breaker(**TEST_PARAMS) b.create(); print b.filepath diff --git a/db.py b/db.py deleted file mode 100755 index 2570b4f..0000000 --- a/db.py +++ /dev/null @@ -1,31 +0,0 @@ -import MySQLdb -USER = "asdfus" -PASSWORD = "gTYgT&M6q" -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 index 991f853..68de55b 100755 --- a/gradient.py +++ b/gradient.py @@ -5,26 +5,11 @@ from subprocess import call import simplejson as json import sys import os -import db import sha +import lib.utils as utils +from config import * -#{{{ 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) -#}}} -def is_color(s): - if s == "": - return "transparent" - if re.match('(rgba?\([0-9]+,[0-9]+,[0-9]+\))|([a-zA-Z]+)|(\#[A-Ha-h0-9]+)', s): - return s.replace(' ','') - else: - raise ValueError PARAM_LIST = [ "width", "height", "color1", "color2", @@ -40,20 +25,14 @@ PARAM_LIST = [ "gradienttype", "username", ] -BIN_CONVERT = "/usr/bin/convert" -BIN_IDENTIFY = "/usr/bin/identify" -BEVELBORDER = "./bevelborder" DEFAULT_FORMAT = "png" DEFAULT_COLORS = { "color1" : "white", "color2" : "black", }; -#WORKING_DIR = '/tmp' -WORKING_DIR = '/var/www/cache' DEFAULT_WIDTH = "200" DEFAULT_HEIGHT = "200" - DEFAULT_BEVEL_PERCENT = "12"; HALFTONEVALUES = { @@ -65,36 +44,9 @@ HALFTONEVALUES = { "flatstripes": "o2x2", } -TEST_FORM = { - "width" : "200", - "color1" : "#ffdead", - "color2" : "blue", - "stripes" : "true", - "stripenumber" : "20", - "gradienttype" : "radial", - "stripeintensity" : "20", - "halftone" : "checkeredfade", - "percentbeveled" : "30", - "flip" : "true", - "bevel" : "flatinner", - "rotate" : "20", - "height" : "200", - "filetype" : "jpg", - "username" : "whatever" -} - -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()) + def __init__(self, **kwargs): self.tag = "imGradient" self.directory = WORKING_DIR self.commands = [] @@ -103,20 +55,20 @@ class Gradient: params = {} for key in PARAM_LIST: - if key in form: + if key in kwargs: if key in ['color1', 'color2']: - params[key] = is_color(form[key]) + params[key] = utils.is_color(kwargs[key]) else: - params[key] = sanitize(form[key]) + params[key] = utils.sanitize(kwargs[key]) if key in ['rotate','tilt','blurriness','stripenumber','stripeintensity']: - params[key] = params[key] if is_number(params[key]) else "" + params[key] = params[key] if utils.is_number(params[key]) else "" elif key in ['brightness', 'contrast', 'hue']: - if not is_number(params[key]) or params[key] == "100": params[key] = "" + if not utils.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['width'] = params['width'] if utils.is_number(params['width']) else DEFAULT_WIDTH + params['height'] = params['height'] if utils.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 @@ -125,25 +77,24 @@ class Gradient: "flatout", "flatinner", "evenlyframed", "biginner", "bigouter", "dramaticflatout", "dramaticflatinner", ] + def newfilename(self): return "{}{}-{}_{}_{}.{}".format( self.tag, self.params['color1'].replace('#','').replace('(','-').replace(')','-'), self.params['color2'].replace('#','').replace('(','-').replace(')','-'), - self.now, + utils.now(), self.params['username'], self.params['filetype'] or DEFAULT_FORMAT, ) - def _call_cmd(self, cmd, error=""): + def _call_cmd(self, cmd): try: - call(cmd) + utils.call_cmd(cmd) self.commands.append(" ".join(cmd)); - except Exception as e: - if error: - sys.stderr.write(error) - else: - sys.stderr.write(str(e)) + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) + def _build_cmd(self): cmd = [BIN_CONVERT] @@ -241,8 +192,24 @@ class Gradient: self._make_bevel() if __name__ == "__main__": - g = Gradient(TEST_FORM); + TEST_FORM = { + "width" : "200", + "color1" : "#ffdead", + "color2" : "blue", + "stripes" : "true", + "stripenumber" : "20", + "gradienttype" : "radial", + "stripeintensity" : "20", + "halftone" : "checkeredfade", + "percentbeveled" : "30", + "flip" : "true", + "bevel" : "flatinner", + "rotate" : "20", + "height" : "200", + "filetype" : "jpg", + "username" : "whatever" + } + g = Gradient(**TEST_FORM); g.create(); - print g.now print " ".join(g.commands) print g.filename diff --git a/grid b/grid deleted file mode 100755 index 58d0e08..0000000 --- a/grid +++ /dev/null @@ -1,241 +0,0 @@ -#!/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/imgrid.py b/imgrid.py index ece50b0..e294066 100755 --- a/imgrid.py +++ b/imgrid.py @@ -1,136 +1,20 @@ #!/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 = "/var/www/cache" -#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"; +import lib.utils as utils +from config import * +import tempfile DEFAULT_HEIGHT = 400 DEFAULT_WIDTH = 600 DEFAULT_LINE_COLOR = "silver" -DEFAULT_FINALFORMAT = "png" - -#{{{Utility functions -def is_color(s): - if s == "": - return "transparent" - if re.match('(rgba?\([0-9]+,[0-9]+,[0-9]+\))|([a-zA-Z]+)|(\#[A-Ha-h0-9]+)', s): - return s.replace(' ', ''); - else: - sys.stderr.write("Not a color: {}\n".format(s)) - raise ValueError -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("IMGRID couldn't determine mimetype") - 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("IMGRID couldn't determine file size") - 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("IMGRID couldn't get gif frames") - sys.stderr.write(str(e)) - raise; -#}}} class Imgrid(): - def __init__(self, params): - self.params = {} + def __init__(self, **kwargs): self.tag = "imGrid" - self.now = now() self.files_created = [] self.commands = []; self._required_keys = [ @@ -160,47 +44,48 @@ class Imgrid(): "username" #}}} ] + + #Work out params ... + #note, tmpfile lib is pretty much useless here, given imagemagick's behavior with gifs (it splits them) etc... + #instead we're just making our own in /var/www/cache (tmpfs mounted there) + self.params = {} 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']) + if k in kwargs: + if k in [ 'bgimage', 'planebgimage', 'imageinstead' ] and utils.bool_correct(kwargs[k]): + self.params[k] = { + 'url' : kwargs[k], + 'filename' : self._make_tempname(k), + 'path' : os.path.join(WORKING_DIR, self._make_tempname(k)) , + } try: - download(self.params[k]['url'], self.params[k]['path']) + utils.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']) + self.params[k]['mimetype'] = utils.get_mimetype(self.params[k]['path']) + frames = utils.gif_frames(self.params[k]['path']) if len(frames) > 1: self.params[k]['path'] = random.choice(frames) - - except Exception as e: - sys.stderr.write("BAD PARAMS\n") + except Exception: sys.stderr.write(str(e)) - raise; + raise Exception ("BAD PARAMS"); elif k in [ 'skycolor', 'bgcolor', 'planebgcolor','linecolor' ]: try: self.params[k] = is_color(params[k]) - except Exception as e: - sys.stderr.write("Unable to process color for:\n") - sys.stderr.write(k) - raise e + except Exception: + raise Exception("Unable to process color for:\n{}".format(k)) elif k == 'opacity': self.params[k] = str(float(params[k])) elif k == 'zoom': self.params[k] = int(float(params[k])) else: - self.params[k] = bool_correct(sanitize(params[k])) + self.params[k] = utils.bool_correct(utils.sanitize(kwargs[k])) else: - self.params[k] = False; - - self.params = dotdict(self.params) + self.params[k] = None; + + self.params = utils.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) @@ -210,21 +95,20 @@ class Imgrid(): def _get_filename(self): return "{}_{}_{}".format( self.tag, - now(), + utils.now(), self.params.username or "" ); - def _call_cmd(self, cmd, error=""): - try: - call(cmd) - self.commands.append(" ".join(cmd)); - except Exception as e: - sys.stderr.write("IT HIT AN ERROR") - sys.stderr.write(str(cmd)) - if error: - sys.stderr.write(error) - else: - sys.stderr.write(str(e)) + def _call_cmd(self, cmd): + try: + utils.call_cmd(cmd) + self.commands.append(" ".join(cmd)); + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) + + def _make_tempname(self, s): + return "IMGRIDTMP{}{}".format(utils.now(), s); + #makes a canvas file...step 1 (if not bgimage) def _make_canvas(self): @@ -332,7 +216,7 @@ class Imgrid(): self._cleanup() if __name__ == "__main__": - g = Imgrid({ + g = Imgrid(**{ 'bgimage' : 'http://i.asdf.us/im/1a/imBreak_1424909483_xx_abridged___.gif', 'planebgimage' : 'http://i.imgur.com/FICZtph.png', 'tilt' : '30', @@ -343,6 +227,4 @@ if __name__ == "__main__": 'trim' : 'true' }) g.create() - print g.now - print g.filepath print g.commands diff --git a/install/pb_nginx.conf b/install/pb_nginx.conf new file mode 100644 index 0000000..994f69a --- /dev/null +++ b/install/pb_nginx.conf @@ -0,0 +1,22 @@ +server { + + server_name asdf.us *.asdf.us; + + access_log /var/www/asdf.us/logs/nginx.access.log; + location / { + proxy_pass http://asdf.us:8002; + client_max_body_size 2252K; + } + location /im/api { + include fastcgi_params; + fastcgi_split_path_info ^()(.*)$; + fastcgi_param PATH_INFO $document_uri; + fastcgi_param QUERY_STRING $document_uri; + fastcgi_param HTTP_X_FORWARDED_HOST "asdf.us"; + fastcgi_pass localhost:8999; + } + location /donjuan/data/ { + rewrite ^/donjuan/data/(.*) http://i.asdf.us/donjuan_data/$1 permanent; + } +} + diff --git a/lib/__init__.py b/lib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/lib/db.py b/lib/db.py new file mode 100755 index 0000000..2570b4f --- /dev/null +++ b/lib/db.py @@ -0,0 +1,31 @@ +import MySQLdb +USER = "asdfus" +PASSWORD = "gTYgT&M6q" +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/lib/utils.py b/lib/utils.py new file mode 100644 index 0000000..a662869 --- /dev/null +++ b/lib/utils.py @@ -0,0 +1,122 @@ +import re +from config import * +import time +import urllib +import urllib2 +import sys +from subprocess import Popen,PIPE,call +Request = urllib2.Request +urlencode = urllib.urlencode +urlopen = urllib2.urlopen + +def call_cmd(cmd, error=""): + try: + call(cmd) + except Exception as e: + raise (str(e)) + +def is_color(s): + if s == "": + return "transparent" + if re.match('(rgba?\([0-9]+,[0-9]+,[0-9]+\))|([a-zA-Z]+)|(\#[A-Ha-h0-9]+)', s): + return s.replace(' ', ''); + else: + sys.stderr.write("Not a color: {}\n".format(s)) + raise ValueError + +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 is_number(s): + try: + return int(s) + except (ValueError, TypeError): + return 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 + +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("couldn't determine mimetype") + 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("IMGRID couldn't determine file size") + 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("IMGRID couldn't get gif frames") + sys.stderr.write(str(e)) + raise; diff --git a/pattern.py b/pattern.py index b622aeb..5470004 100755 --- a/pattern.py +++ b/pattern.py @@ -1,121 +1,21 @@ #!/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 +import urllib +from config import * +import lib.utils as utils import simplejson as json from PIL import Image import uuid -WORKING_DIR = "/tmp" -#WORKING_DIR = "/var/www/cache" -BIN_COMPOSITE = "/usr/bin/composite" -BIN_CONVERT = "/usr/bin/convert" -BIN_IDENTIFY = "/usr/bin/identify" -DEFAULT_FINALFORMAT = "png"; - -MAX_SIZE = 1024 * 1024 * 1.2 * 1.5 - -#FIXME make test params FUSE_MODE="Pin_Light" -TEST_PARAMS = { -# "pattern_url" : "http://asdf.us/impattern/patterns/1.png", - "pattern_data" : '{"matrix":[["0","0","0","0","0","1","0","0","0","0"],["0","0","0","0","1","1","1","0","0","0"],["0","0","1","1","1","0","1","0","0","0"],["0","1","1","0","0","0","0","0","0","0"],["0","1","0","0","1","0","0","0","0","0"],["0","1","0","0","1","0","0","0","1","0"],["0","1","0","0","1","1","0","0","1","0"],["0","1","0","0","0","1","1","1","1","0"],["0","1","1","1","1","0","0","0","0","0"],["0","0","0","0","1","0","0","0","0","0"]],"width":"10","height":"10"}', -# "username" : "garfield", -# "image_url" : "http://i.asdf.us/im/be/PinkHijab_1425078647_reye.gif", -} - -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 Pattern: - def __init__(self, params): + def __init__(self, **kwargs): self.params = {} - self.now = now() self.tag = "imPattern"; self._pid = str(os.getpid()) self.commands = []; @@ -131,20 +31,20 @@ class Pattern: ] self.files_created = [] for k in self._required_keys: - if k in params: + if k in kwargs: if k in [ 'pattern_url', 'image_url' ]: - self.params[k] = params[k] + self.params[k] = kwargs[k] elif k == 'pattern_data': - self.params[k] = params[k] #FIXME add conversion data + self.params[k] = kwargs[k] #FIXME add conversion data else: - self.params[k] = sanitize(params[k]) + self.params[k] = utils.sanitize(kwargs[k]) else: self.params[k] = False; if not self.params['image_url']: sys.stderr.write('no image url'); raise ValueError - self.params = dotdict(self.params) + self.params = utils.dotdict(self.params) self.basename, self._format = self._get_filename(); #FIXME omit file extension for downloaded files @@ -154,7 +54,7 @@ class Pattern: self._download(self.params.image_url, self._downloaded_file) - self.width, self.height = dimensions(self._downloaded_file) # same here + self.width, self.height = utils.dimensions(self._downloaded_file) # same here self.filename = "{}.{}".format(self.basename, self._format) self.filepath = os.path.join(WORKING_DIR, self.filename) @@ -169,21 +69,18 @@ class Pattern: def _download(self, url, dest): try: - download(url, dest) + utils.download(url, dest) self.files_created.append(dest) except Exception as e: sys.stderr.write(str(e)) raise; - def _call_cmd(self, cmd, error=""): - try: - call(cmd) - self.commands.append(" ".join(cmd)); - except Exception as e: - if error: - sys.stderr.write(error) - else: - sys.stderr.write(str(e)) + def _call_cmd(self, cmd): + try: + utils.call_cmd(cmd) + self.commands.append(" ".join(cmd)); + except Exception: + raise Exception("Unable to call cmd {}".format(str(cmd))) def _from_pattern_data(self): def boolToColor(boolean): @@ -215,8 +112,8 @@ class Pattern: name_part = url.split("/")[-1] try: parts = name_part.split(".") - name_part = sanitize(parts[-2]) - file_format = sanitize(parts[-1]) + name_part = utils.sanitize(parts[-2]) + file_format = utils.sanitize(parts[-1]) if not name_part or not file_format: sys.stderr.write( "Incompatible input file type") raise; @@ -228,7 +125,7 @@ class Pattern: 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 + return "{}{}_{}_{}".format(self.tag, name_part, utils.now(), self.params.username or ""), file_format def _cleanup(self): cmd = ["rm"]+self.files_created @@ -263,6 +160,12 @@ class Pattern: self._fuse_mask(); if __name__ == "__main__": - p = Pattern(TEST_PARAMS) + TEST_PARAMS = { + # "pattern_url" : "http://asdf.us/impattern/patterns/1.png", + "pattern_data" : '{"matrix":[["0","0","0","0","0","1","0","0","0","0"],["0","0","0","0","1","1","1","0","0","0"],["0","0","1","1","1","0","1","0","0","0"],["0","1","1","0","0","0","0","0","0","0"],["0","1","0","0","1","0","0","0","0","0"],["0","1","0","0","1","0","0","0","1","0"],["0","1","0","0","1","1","0","0","1","0"],["0","1","0","0","0","1","1","1","1","0"],["0","1","1","1","1","0","0","0","0","0"],["0","0","0","0","1","0","0","0","0","0"]],"width":"10","height":"10"}', + # "username" : "garfield", + "image_url" : "http://i.asdf.us/im/be/PinkHijab_1425078647_reye.gif", + } + p = Pattern(**TEST_PARAMS) p.create() diff --git a/pb_nginx.conf b/pb_nginx.conf deleted file mode 100644 index 994f69a..0000000 --- a/pb_nginx.conf +++ /dev/null @@ -1,22 +0,0 @@ -server { - - server_name asdf.us *.asdf.us; - - access_log /var/www/asdf.us/logs/nginx.access.log; - location / { - proxy_pass http://asdf.us:8002; - client_max_body_size 2252K; - } - location /im/api { - include fastcgi_params; - fastcgi_split_path_info ^()(.*)$; - fastcgi_param PATH_INFO $document_uri; - fastcgi_param QUERY_STRING $document_uri; - fastcgi_param HTTP_X_FORWARDED_HOST "asdf.us"; - fastcgi_pass localhost:8999; - } - location /donjuan/data/ { - rewrite ^/donjuan/data/(.*) http://i.asdf.us/donjuan_data/$1 permanent; - } -} - diff --git a/pbserver.py b/pbserver.py index e7f190a..02a17ed 100755 --- a/pbserver.py +++ b/pbserver.py @@ -6,12 +6,11 @@ from imgrid import Imgrid from breaker import Breaker from pattern import Pattern -from s3config import AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, BUCKET_NAME +from config import AWS_SECRET_ACCESS_KEY, AWS_ACCESS_KEY_ID, BUCKET_NAME import os import sys -import db -import s3 +import lib.db as db import mimetypes import sha from subprocess import call, Popen, PIPE @@ -20,12 +19,12 @@ BIN_IDENTIFY = "/usr/bin/identify" from boto.s3.connection import S3Connection from boto.s3.key import Key - -try: - DB = db.db () -except Exception as e: - sys.stderr.write("Could not connect to db:\n{}".format(e)) - sys.exit(1); +# +#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): @@ -113,7 +112,7 @@ def return_image(im, insert_url="NULL"): @post('/im/api/imgradient') def gradient(): try: - im = Gradient(request.forms) + im = Gradient(**(dict(request.forms))) im.create(); return return_image(im) except Exception as e: diff --git a/s3.py b/s3.py deleted file mode 100644 index 0f2489d..0000000 --- a/s3.py +++ /dev/null @@ -1,618 +0,0 @@ -#!/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=443, 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 = "" + \ - location + \ - "" - 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 deleted file mode 100644 index 49357ad..0000000 --- a/s3config.py +++ /dev/null @@ -1,3 +0,0 @@ -AWS_ACCESS_KEY_ID = 'AKIAIR53VPBXKJMXZIBA' -AWS_SECRET_ACCESS_KEY = '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' -BUCKET_NAME = 'i.asdf.us' -- cgit v1.2.3-70-g09d2