#!/usr/bin/perl # # (JFIF) IMAGE MUCKER 3/5/2007 my $VERSION = '1.0'; # # # USAGE: # imgmuck.pl INFILE OUTFILE # # ARGUMENTS: # --destroy {percentage} Muck image stream data. # --quant {percentage} Muck quantization table. # --help # # CONCEPT: # Too many people are creating "glitch art" by simply poking and proding # at data files. If the recepient software does not understand what you've # given it, it will hang/crash/explode... This is simply a chaotic JFIF # "glitch" manipulator with bounds created by the files specification. # Meaning, most, if not all pieces of software capable of reading the # initial input will be capable of reading the output. # # # AUTHOR: # C. Bratcher # # # # LICENSE / WARRANTY: # GPL version 2 # Basically, use this as you want. If you do something interesting, # let me know. If you alter this, give credit where it's due. # There is no warranty, use at your own risk... # # # NOTE: # -Currently only works on jpeg/exif files. # -This will overwrite WITHOUT asking! # -Values of 0xFF are skipped... JFIF forces a true 0xFF value to be # followed with a 0x00. Ignoring them entirely fixes the problem. # # # TODO: # Add more formats... Maybe... # # use strict; use warnings; use Getopt::Long; my $percentDestruction = 0; my $muckQuantPercent = 0; my $verbosity = 0; my $help = 0; GetOptions("quant|q=i" => \$muckQuantPercent, "destroy|d=i" => \$percentDestruction, "v+" => \$verbosity, "help|h+" => \$help); my $inFile = shift; my $outFile = shift; sub get_data($$); # $offset, $length sub is_null($); # $offset if ( !$inFile || !$outFile || $help) { die " JFIF Image Mucker v$VERSION Usage: $0 INFILE OUTFILE Structured 'glitch' art based on random alterations within spec. Optional Arguments: \t--destroy {percentage}\tMuck image stream. \t--quant {percentage}\tMuck quantization table. \t--help\tThis display. Example: $0 DSC1234.JPG mucked.jpg --destroy 20 --quant 2 "; } elsif ( ! -e $inFile ) { die "Error! File \"$inFile\" does not exist!\n"; } elsif (! -r $inFile ) { die "Error! Cannot read from file \"$inFile\"!\n"; } open(FILE, "<$inFile"); binmode(FILE); my $fileSize = ( -s $inFile ); my ($offset, $length); my $quantData = ''; my $header = ''; my $quantOffset = 0; my $hdr; my $data = get_data(0, 2); if ( unpack("H4",$data) eq 'ffd8' ) { $offset = 4; $data = get_data ($offset, 2); while ( unpack("H4",$data) ne 'ffda' ) { $data = get_data($offset, 2); $hdr = unpack("H4",$data); if ( $hdr eq 'ffdb' ) { # quantization table $offset += 2; if ($muckQuantPercent > 0) { # Quant table always 64 bytes my $quantSize = unpack("n4",get_data($offset,2)); $quantData = get_data($offset+2, 64); $quantOffset = $offset + ($quantSize - 64); my $totalQuantManips = int(64 * ($muckQuantPercent / 100)); my %muckQuants; my @quantOffsets; my $muckQuantByte; for (my $i = 0; $i < $totalQuantManips; $i++) { $muckQuantByte = int(rand(64)); if ( exists $muckQuants{$muckQuantByte} ) { $i--; } else { $quantOffsets[$i] = $muckQuantByte; $muckQuants{$muckQuantByte} = pack("C",int(rand(255))); # NO 0xFF } } @quantOffsets = sort {$a <=> $b} @quantOffsets; for (my $i = 0; $i <= $#quantOffsets; $i++) { $quantData = substr($quantData,0,$quantOffsets[$i]).$muckQuants{$quantOffsets[$i]}.substr($quantData,$quantOffsets[$i]+1); } $offset += $quantSize; } } elsif ( substr($hdr,0,2) eq 'ff' ) { $offset += 2; } else { $offset += ( unpack("n4",$data) ); # big-endianess } } $offset += 2; # leave ffda size alone $header = get_data(0,$offset); if ($quantData) { $header = substr($header,0,$quantOffset).$quantData.substr($header,64+$quantOffset); } open (OUTF, ">$outFile") || die $!; print OUTF $header; # Such hard work... if ($percentDestruction > 0) { my $muckByte; my %muckBytes; my @muckOffsets; my $totalBytes = $fileSize - $offset; my $totalManips = int($totalBytes * ($percentDestruction / 1000000)); for (my $i = 0; $i < $totalManips; $i++) { $muckByte = int(rand($totalBytes) + $offset); if ( exists $muckBytes{$muckByte} ) { $i--; # No dupes } else { $muckOffsets[$i] = $muckByte; if ( is_null($muckByte) ) { $muckBytes{$muckByte} = "\0"; } else { $muckBytes{$muckByte} = pack("C",int(rand(255))); # NO 0xFF } } } my $rawDataLength; @muckOffsets = sort {$a <=> $b} @muckOffsets; # Save mucked data for (my $i = 0; $i <= $#muckOffsets; $i++) { if ( is_null($muckOffsets[$i]) ) { $muckBytes{$muckOffsets[$i]} = "\0"; } if ( $i != $#muckOffsets ) { $rawDataLength = $muckOffsets[$i + 1] - $muckOffsets[$i] - 1; } else { $rawDataLength = $fileSize - $muckOffsets[$i] - 1; } # First element is directly after offset or another element if ( ($i == 0 && $muckOffsets[$i] == $offset + 1) || $rawDataLength == 0 ) { print OUTF $muckBytes{$muckOffsets[$i]}; } elsif ( $i == 0 ) { print OUTF get_data($offset,$muckOffsets[$i]-$offset); print OUTF $muckBytes{$muckOffsets[$i]}; print OUTF get_data($muckOffsets[$i]+1,$rawDataLength); } else { print OUTF $muckBytes{$muckOffsets[$i]}; print OUTF get_data($muckOffsets[$i]+1,$rawDataLength); } } } else { for (my $i = 0; $i <= int(($fileSize-$offset)/1024); $i++) { print OUTF get_data(($i*1024)+$offset,1024); } } close (OUTF); } else { print "File \"$inFile\" isn't a JFIF (JPEG/EXIF)!\n"; } close(FILE); sub get_data ($$) { my $out = ''; my ($offset, $length) = @_; seek (FILE, $offset, 0); read (FILE, $out, $length); return $out; } # If a byte is null, there's a good chance the byte preceeding it # is 0xFF. Simply asuming all null bytes are this way increases # speed. sub is_null ($) { my ($offset) = @_; if ( get_data($offset, 1) ) { return 1; } else { return 0; } }