Last active
October 31, 2019 16:55
-
-
Save tayl0r/bc04916611a2c3af4d63d62c4832720e to your computer and use it in GitHub Desktop.
resizes images to the nearest multiple of 4
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/perl | |
# license: do whatever you want | |
# author: taylor steil https://github.com/tayl0r | |
# purpose: resizes images to a multiple of 4. | |
# will resize them the least amount possible and also keep the aspect ratio as close to the original as possible | |
# will also pngcrunch the image | |
# this is useful for using images in ETC2 Crunch compression, which requires image sizes to be a multiple of 4 | |
# requirement: imagemagick (should be able to apt-get install imagemagick) | |
# requirement: pngcrush (should be able to apt-get install pngcrush) | |
# tested on windows 10 WSL (windows-linux-subsystem which is some variant of ubuntu) | |
# usage: perl imageResizerMultipleOf4.pl /some/folder/with/images | |
use strict; | |
use File::Find; | |
use File::Copy; | |
use File::Path; | |
use Digest::MD5 qw(md5); | |
use Cwd 'abs_path'; | |
my $path = $ARGV[0] or die("1st arg: root path is required, will search recursively"); | |
print "path=$path\n"; | |
find(\&FindImages, $path); | |
sub ValidDirectory { | |
my $subDir = $_[0]; | |
my $firstChar = substr($subDir, 0, 1); | |
if ($subDir eq '.' || $subDir eq '..' || $subDir eq '_' || $subDir eq '.DS_Store' || $subDir eq '.BridgeSort' || | |
$firstChar eq '.' || $firstChar eq '#' || $firstChar eq '.' || $firstChar eq '_') { return 0; } | |
return 1; | |
} | |
sub FindImages { | |
my $fileName = $_; | |
my $fullDir = $File::Find::dir; | |
my $fullName = $File::Find::name; | |
#print "$fullName\n"; | |
if ($fullDir eq $fullName) { return; } | |
if (-d $fullName) { return; } | |
if (!($fileName =~ m/\.png$/g)) { return; } | |
my @dirParts = split /\//, $fullName; | |
foreach my $dirPart (@dirParts) { | |
if (&ValidDirectory($dirPart) == 0) { return; } | |
} | |
my $partialDir = $fullDir; | |
$partialDir =~ s/\Q$path//; | |
my $fileNameLocal = "$partialDir/$fileName"; | |
$fileNameLocal =~ s/^\///; | |
print "$fileNameLocal\n"; | |
my $cmd; | |
my $output; | |
# get image size | |
$cmd = qq|identify -format "%W %H" "$fileName"|; | |
$output = `$cmd`; | |
chomp $output; | |
if ($output =~ /(\d+) (\d+)/) { | |
my $width = $1; | |
my $startWidth = $width; | |
my $height = $2; | |
my $startHeight = $height; | |
# get the mod of each number by 4, that tells us if its a multiple of 4 and how close away it is | |
my $widthMod = $width % 4; | |
my $heightMod = $height % 4; | |
if ($widthMod == 0 && $heightMod == 0) { | |
# we good | |
next; | |
} | |
# first, figure out which mod is lower, that is the one we want to use for both | |
if ($widthMod == $heightMod) { | |
# same | |
$width = &GetNearestMultiple4($width, $widthMod, $widthMod); | |
$height = &GetNearestMultiple4($height, $heightMod, $heightMod); | |
} elsif (($widthMod == 1 || $widthMod == 3) && ($heightMod == 1 || $heightMod == 3)) { | |
# we have a 1 and a 3, that means we should make both go up | |
$width = &GetNearestMultiple4($width, 3, $widthMod); | |
$height = &GetNearestMultiple4($height, 3, $heightMod); | |
} elsif ($widthMod < $heightMod) { | |
# width mod is lower, use width mod as the control for both | |
$width = &GetNearestMultiple4($width, $widthMod, $widthMod); | |
$height = &GetNearestMultiple4($height, $widthMod, $heightMod); | |
} else { | |
# height mod is lower, use height mod as the control for both | |
$width = &GetNearestMultiple4($width, $heightMod, $widthMod); | |
$height = &GetNearestMultiple4($height, $heightMod, $heightMod); | |
} | |
my $realWidthMod = $width - $startWidth; | |
my $realHeightMod = $height - $startHeight; | |
print "\t$startWidth -> $width ($realWidthMod), $startHeight -> $height ($realHeightMod)\n"; | |
# now do some checks to make sure our logic is solid | |
$widthMod = $width % 4; | |
$heightMod = $height % 4; | |
# check if both our numbers are multiples of 4 | |
if ($widthMod != 0 || $heightMod != 0) { | |
die("not multiple of 4"); | |
} | |
# check if both mods are 1 or 2, we should have no more 3s | |
if ($widthMod >= 3 || $heightMod >= 3) { | |
die("mod too high. $widthMod,$heightMod"); | |
} | |
# check if both numbers changed in the same direction | |
if ($widthMod != 0 && $heightMod != 0 && ($startWidth - $width) / ($startWidth - $width) != ($startHeight - $height) / ($startHeight - $height)) { | |
die("didnt go in same direction. $widthMod,$heightMod"); | |
} | |
# should never have a cumulative sum of -4 or more | |
if ($realWidthMod != 2 && ($realWidthMod + $realHeightMod) <= -4) { | |
die("sum mod >= 4. $realWidthMod,$realHeightMod"); | |
} | |
# never want to be within 8 higher of a power of 2, otherwise we should probably go to the nearest power of 2 instead | |
# this script does not handle this, just do it manually | |
foreach my $pow2 (256, 512, 1024, 2048, 4096) { | |
my $widthPow2 = $width - $pow2; | |
my $heightPow2 = $height - $pow2; | |
if (($width >= $height && $widthPow2 > 0 && $widthPow2 <= 8) || ($height >= $width && $heightPow2 > 0 && $heightPow2 <= 8)) { | |
die("too close to power of 2: $pow2"); | |
} | |
} | |
# finally, actually do the resize | |
my $widthXheight = "$width" . "x" . "$height"; | |
$cmd = qq|convert "$fileName" -geometry $widthXheight\! "$fileName" 2>&1|; | |
print "\t$cmd\n"; | |
$output = `$cmd`; | |
if ($?) { | |
print "ERROR: $output\n"; | |
} | |
# and pngcrush it | |
$cmd = qq|pngcrush -ow "$fileName" 2>&1|; | |
print "\t$cmd\n"; | |
$output = `$cmd`; | |
if ($?) { | |
print "ERROR: $output\n"; | |
} | |
} | |
} | |
# size = width or height | |
# mod & diff are both the modulus by 4, which tels us how far away we are from a multiple of 4 | |
# mod is what controls if we will go up or down | |
# diff is how many we need to go up or down by | |
sub GetNearestMultiple4 { | |
my ($size, $mod, $diff) = @_; | |
# if we have a 0 mod, we should use our diff instead | |
if ($mod == 0) { | |
$mod = $diff; | |
} | |
if ($diff == 0) { | |
# do nothing with a 0 diff | |
} elsif ($mod == 1) { | |
# if we're just 1 off, then we want to go down and not up. so 557 becomes 556 | |
$size = $size - $diff; | |
} elsif ($mod == 2 || $mod == 3) { | |
# if we're 2 or 3 off, we want to go up. so 902 or 903 becomes 904 | |
$size = $size + (4 - $diff); | |
} | |
return $size; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment