Monday, April 30, 2012

KirSizer - Flex AIR Image Sizer app: Part 14

In this tutorial we will make the log text area refresh when it reaches bottom and add an option to skip files if their size is already correct.

When I was testing KirSizer on a big amount of images, I noticed that quite a lot of images already had the correct size and thought that it would take less time if I just checked if the picture's size are already correct and skip them. However, just skipping all files for just having correct sizes is wrong, since KirSizer is not only capable of resizing images, but also renaming them, changing their format and also compress pictures. That is why I decided to include the "Skip" feature as a toggleable option.

I also noticed that the log text can get quite big. To prevent any problems with too large text, I decided to clear the text area every time it reaches 24 lines. This way it is still informative, but takes much less space and perhaps this way it even is more comprehendable.

First we'll do the text limitation feature. Create a function called checkLineLimit() which checks if the number of lines exceeds 24 and set logArea's text to blank if so.

private function checkLineLimit():void {
if (countLines(logArea.text) > 24) {
logArea.text = "";
}
}

The countLines() function that I used returns an integer value, which equals to the number of elements of the array that's created by splitting the text area's text by new line characters:

private function countLines(text:String):int {
var splittedText:Array = text.split("\n");
var lines:int = splittedText.length;
return lines;
}

We call this function in the beginning of nextAction(), in its first if statement:

private function nextAction():void {
if (currentNum < selectedFiles.length) {
checkLineLimit();
var isFolder:Boolean = false;
var folder:File = new File(selectedFiles[currentNum].path);
// if its a folder, create a global array that stores all image files of the folder
if (folderArray.length > 0) isFolder = true;
if (folder.exists && selectedFiles.length > currentNum && selectedFiles[currentNum].type == 1 && folderArray.length == 0) {
isFolder = true;
folderArray = [];
var allContent:Array = folder.getDirectoryListing();
for (var i:int = 0; i < allContent.length; i++) {
if (allContent[i].isDirectory == false && (allContent[i].extension.toLowerCase() == "png" || allContent[i].extension.toLowerCase() == "jpg" || allContent[i].extension.toLowerCase() == "jpeg")) {
folderArray.push(allContent[i]);
}
}
if (selectedFiles[currentNum].num != folderArray.length) {
appendHighlight("ERROR: incorrect amount of image files found in folder " + folder.name + "\n", 0xff2222);
errorText += "Not enough or too many image files found in folder " + folder.nativePath + " (some files were removed/added before it was this folder's turn to be resized)" + File.lineEnding;
totalErrors++;
}
}
if (!folder.exists && selectedFiles.length > currentNum && selectedFiles[currentNum].type == 1 && folderArray.length == 0) {
appendHighlight("ERROR: folder not found (" + folder.name + ")\n", 0xff2222);
errorText += "Folder was not found at " + folder.nativePath + File.lineEnding;
totalErrors++;
currentNum++;
nextAction();
return;
}
resizeNext(isFolder);
}else {
progressBar.setProgress(totalFiles, totalFiles);
appendHighlight("Operation complete!\nErrors: " + totalErrors + "\nFiles resized: " + successFiles + "/" + totalFiles, 0xffff44);
errorText += "----------------------" + File.lineEnding + "Total errors: " + totalErrors + File.lineEnding + "Files resized: " + successFiles + "/" + totalFiles;
buttonError.enabled = true;
buttonReturn.enabled = true;
statusText.setStyle("color", 0x44ff44);
statusText.text = "COMPLETED";
}
}

Now let's work on the skipping feature. Go to the options screen's NavigatorContent object and create a new CheckBox object with an id skipCorrect and selected property set to false:

<s:NavigatorContent width="100%" height="100%" hideEffect="fadeOut" showEffect="fadeIn">
<s:VGroup width="100%" height="100%" paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10">
<s:Button label="Return to file selection" width="100%" click="contentStack.selectedIndex = 0;" />

<s:Label>Resize options:</s:Label>

<mx:ComboBox width="100%" id="actionCombo" height="22" dataProvider="{actions}" selectedIndex="0" editable="false" change="actionChange();"
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>
<s:HGroup verticalAlign="middle">
<s:Label width="50">Width:</s:Label>
<s:NumericStepper id="newWidth" height="22" width="150" minimum="1" value="100" maximum="{(widthMeasure.selectedIndex==0)?(100):(4000)}" />
<mx:ComboBox id="widthMeasure" height="22" width="50" dataProvider="{measures}" selectedIndex="1" editable="false" 
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>
</s:HGroup>

<s:HGroup verticalAlign="middle">
<s:Label width="50">Height:</s:Label>
<s:NumericStepper id="newHeight" height="22" width="150" minimum="1" value="100" maximum="{(heightMeasure.selectedIndex==0)?(100):(4000)}"/>
<mx:ComboBox id="heightMeasure" height="22" width="50" dataProvider="{measures}" selectedIndex="1" editable="false" 
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>
</s:HGroup>

<s:CheckBox id="skipCorrect" label="Skip file if the size is correct" selected="false" />

<s:Label/>

<s:Label>Output file names:</s:Label>
<s:HGroup verticalAlign="middle">
<s:TextInput id="nameInput" width="240" text="%initialName%" />
<s:Button width="35" label="?" click="contentStack.selectedIndex = 2;" />
</s:HGroup>

<s:Label>Output destination:</s:Label>
<s:HGroup verticalAlign="middle">
<s:RadioButton id="oldDestination" label="Same directory" groupName="destinationGroup" selected="true" />
<s:RadioButton id="newDestination" label="Specified directory" groupName="destinationGroup" />
</s:HGroup>
<s:HGroup verticalAlign="middle" width="100%">
<s:TextInput width="100%" id="destinationInput" enabled="{newDestination.selected}" text="Select destination..." editable="false" />
<s:Button width="80" label="Browse" enabled="{newDestination.selected}" click="pickDestination();" />
</s:HGroup>

<s:Label/>

<s:Label>Output format:</s:Label>
<mx:ComboBox width="100%" height="22" id="formatCombo" dataProvider="{formats}" selectedIndex="0" editable="false" 
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>

<s:Label/>

<s:Label>Output JPG quality:</s:Label>
<s:HSlider id="qualitySlider" width="100%" minimum="1" maximum="100" value="100" />

<s:Label/>

<s:Button label="Resize" width="100%" click="beginResize();" />
</s:VGroup>
</s:NavigatorContent>

Now we're going to add some major changes to the resizeNext() function.

However, the code that we add will not be very big. We're just going to add a needResize boolean value which is set to false if the new height and width equal the old height and width of the image. We are, however, going to have to move large piece of code from the onTimer() function into the resizeNext() function itself.

We are doing this so that we can calculate new size of the picture before starting the timer and wasting time on it. We just load the image, then set all the variables, such as newExtension, currentW, currentH, newW, newH, ratio, newName and needResize, calculate their values, check if the size has changed, and if it hasn't - don't even bother with the timer and just call nextAction(). Otherwise, start the timer and resize normally.

private function resizeNext(isFolder:Boolean):void {
var file:File;
var canProceed:Boolean = true;
var loader:Loader;
var currentProgress:int = progressBar.value + 1;
progressBar.setProgress(currentProgress, totalFiles);

// if its a file
if (!isFolder){
currentNum++;
file = new File(selectedFiles[currentNum - 1].path);
}

// if its a folder
if (isFolder) {
file = folderArray.pop();
if (folderArray.length == 0) {
currentNum++;
}
}

if(file!=null) logArea.appendText("#" + currentProgress + " (" + file.name + ")\n");

// Error: file not found
if (file != null && !file.exists) {
canProceed = false;
appendHighlight("ERROR: File not found\n", 0xff2222);
errorText += "File not found at location: " + file.nativePath + File.lineEnding;
totalErrors++;
nextAction();
}


// Error: bad extension
if (file != null && (file.exists && file.extension.toLowerCase() != "png" && file.extension.toLowerCase() != "jpg" && file.extension.toLowerCase() != "jpeg")) {
canProceed = false;
appendHighlight("ERROR: Incorrect extension\n", 0xff2222);
errorText += "Incorrect file extension: " + file.nativePath + File.lineEnding;
totalErrors++;
nextAction();
}

if (file == null) { 
canProceed = false
nextAction();
};

var newExtension:String;
var currentW:int;
var currentH:int;
var newW:int;
var newH:int;
var ratio:Number;
var newName:String

var needResize:Boolean = true;

// load image
if (canProceed) {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
loader.load(new URLRequest(file.url));
}

// start timer
function onLoadComplete(evt:Event):void {
// new name
newName = nameInput.text.replace("%initialName%", file.name.substr(0, file.name.lastIndexOf('.')));
newName = newName.replace("%num%", currentProgress);

// new extension
if (formatCombo.selectedIndex == 0) newExtension = file.extension;
if (formatCombo.selectedIndex == 1) newExtension = "jpg";
if (formatCombo.selectedIndex == 2) newExtension = "png";

// new size
currentW = loader.content.width;
currentH = loader.content.height;

if (actionCombo.selectedIndex == 0) {
if (widthMeasure.selectedIndex == 0) newW = newWidth.value/100 * currentW;
if (widthMeasure.selectedIndex == 1) newW = newWidth.value;
if (heightMeasure.selectedIndex == 0) newH = newHeight.value/100 * currentH;
if (heightMeasure.selectedIndex == 1) newH = newHeight.value;
}

if (actionCombo.selectedIndex == 1) {
ratio = currentW / currentH;
if (widthMeasure.selectedIndex == 0) newW = newWidth.value/100 * currentW;
if (widthMeasure.selectedIndex == 1) newW = newWidth.value;
newH = newW / ratio;
}

if (actionCombo.selectedIndex == 2) {
ratio = currentW / currentH;
if (heightMeasure.selectedIndex == 0) newH = newHeight.value/100 * currentH;
if (heightMeasure.selectedIndex == 1) newH = newHeight.value;
newW = newH * ratio;
}

if (actionCombo.selectedIndex == 3) {
ratio = currentW / currentH;
newW = newWidth.value;
newH = newW / ratio;
if (newH > newHeight.value) {
newH = newHeight.value;
newW = ratio * newH;
}
}

if (skipCorrect.selected && currentW == newW && currentH == newH) {
needResize = false;
logArea.appendText("Skipped: " + file.name + "\n");
nextAction();
}

if(needResize){
var timer:Timer = new Timer(200, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
timer.start();
}
}

function onTimer(evt:TimerEvent):void {
// log
logArea.appendText("Resized from " + currentW + "x" + currentH + " to " + newW + "x" + newH + "\n");
logArea.appendText("Renamed to " + newName + "." + newExtension + "\n");

// extract
var matrix:Matrix = new Matrix();
matrix.scale(newW/currentW, newH/currentH);
var bitmapData:BitmapData = new BitmapData(newW, newH, false, 0xffffff);
bitmapData.draw(loader.content, matrix, null, null, null, true);

var encoder:IImageEncoder;
if (newExtension == "jpg") encoder = new JPEGEncoder(qualitySlider.value);
if (newExtension == "png") encoder = new PNGEncoder();
var byteArray:ByteArray = encoder.encode(bitmapData);

var newfile:File;
if (newDestination.selected) newfile = new File(destinationPicked + File.separator + newName + "." + newExtension);
if (oldDestination.selected) newfile = new File(file.parent.nativePath + File.separator + newName + "." + newExtension);
var stream:FileStream = new FileStream();
stream.open(newfile, FileMode.WRITE);
stream.writeBytes(byteArray);
stream.close();
successFiles++;

nextAction();
}

}

Full code:

<?xml version="1.0" encoding="utf-8"?>
<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
xmlns:s="library://ns.adobe.com/flex/spark"
xmlns:mx="library://ns.adobe.com/flex/mx"
width="300" height="460" 
showStatusBar="false" title="KirSizer" creationComplete="init();">
   
<fx:Declarations>
<mx:ArrayCollection id="measures">
<fx:String>%</fx:String>
<fx:String>px</fx:String>
</mx:ArrayCollection>
<mx:ArrayCollection id="actions">
<fx:String>Fixed width, fixed height</fx:String>
<fx:String>Fixed width, proportional height</fx:String>
<fx:String>Proportional width, fixed height</fx:String>
<fx:String>Proportional sizes to fit specified sizes</fx:String>
</mx:ArrayCollection>
<mx:ArrayCollection id="formats">
<fx:String>Same format as initial file</fx:String>
<fx:String>Convert all to JPG</fx:String>
<fx:String>Convert all to PNG</fx:String>
</mx:ArrayCollection>
<mx:Fade id="fadeIn" alphaFrom="0" alphaTo="1" duration="300"/>
<mx:Fade id="fadeOut" alphaFrom="1" alphaTo="0" duration="300"/>
<mx:TitleWindow id="waitWindow" title="Please wait" width="240" height="70" showCloseButton="false">
<s:Group width="100%" height="100%">
<s:Label top="10" left="10" id="waitLabel" width="220" color="0x000000" />
</s:Group>
</mx:TitleWindow>
</fx:Declarations>

<fx:Style>
@namespace s "library://ns.adobe.com/flex/spark";
@namespace mx "library://ns.adobe.com/flex/mx";

#contentStack{
backgroundColor: #313131;
}

s|Label{
color: #fcfcfc;
}

s|Button{
chromeColor: #636363;
}

mx|ComboBox{
chromeColor: #636363;
color: #fcfcfc;
contentBackgroundColor: #000000;
rollOverColor: #aaaaaa;
selectionColor: #ffffff;
}

#logArea{
contentBackgroundColor: #111111;
focusedTextSelectionColor: #0000ff;
fontFamily: "Courier New";
color: #aaaaaa;
}
</fx:Style>

<fx:Script>
<![CDATA[
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.events.Event;
import flash.events.FileListEvent;
import flash.events.KeyboardEvent;
import flash.events.TimerEvent;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.geom.Matrix;
import flash.net.FileFilter;
import flash.net.URLRequest;
import flash.system.ImageDecodingPolicy;
import flash.text.TextFormat;
import flash.utils.ByteArray;
import flash.utils.Timer;
import mx.collections.ArrayCollection;
import mx.controls.Image;
import mx.effects.easing.Linear;
import mx.controls.Alert;
import mx.events.CloseEvent;
import mx.events.FlexEvent;
import mx.events.StateChangeEvent;
import mx.graphics.codec.IImageEncoder;
import mx.graphics.codec.JPEGEncoder;
import mx.graphics.codec.PNGEncoder;
import mx.managers.PopUpManager;
import flashx.textLayout.formats.TextLayoutFormat
[Bindable]
private var selectedFiles:ArrayCollection = new ArrayCollection([]);
private var totalFiles:int;
private var currentNum:int;
private var totalErrors:int;
private var folderArray:Array = [];
private var destinationPicked:String = "";
private var errorText:String;
private var successFiles:int;

private function init():void {
addEventListener(KeyboardEvent.KEY_DOWN, keybDown);
}

private function keybDown(evt:KeyboardEvent):void {
if (evt.ctrlKey && evt.keyCode == 65) {
var arr:Array = [];
for (var i:int = 0; i < selectedFiles.length; i++) {
arr.push(i);
}
tileList.selectedIndices = arr;
}
}

private function actionChange():void{
switch (actionCombo.selectedIndex) {
case 0:
newWidth.enabled = true;
widthMeasure.enabled = true;
newHeight.enabled = true;
heightMeasure.enabled = true;
break;
case 1:
newWidth.enabled = true;
widthMeasure.enabled = true;
newHeight.enabled = false;
heightMeasure.enabled = false;
break;
case 2:
newWidth.enabled = false;
widthMeasure.enabled = false;
newHeight.enabled = true;
heightMeasure.enabled = true;
break;
case 3:
newWidth.enabled = true;
widthMeasure.enabled = false;
newHeight.enabled = true;
heightMeasure.enabled = false;
widthMeasure.selectedIndex = 1;
heightMeasure.selectedIndex = 1;
}
}

private function addFiles():void {
var file:File = new File();
file.browseForOpenMultiple("Select JPG or PNG files", [new FileFilter("Pictures", "*.jpg;*.jpeg;*.png")]);
file.addEventListener(FileListEvent.SELECT_MULTIPLE, filesSelected);
}

private function filesSelected(evt:FileListEvent):void {
PopUpManager.addPopUp(waitWindow, this);
PopUpManager.centerPopUp(waitWindow);
waitLabel.text = "Selecting files...";
var timer:Timer = new Timer(500, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, onWait);
timer.start();
function onWait(ev:TimerEvent):void {
doFiles(evt.files);
PopUpManager.removePopUp(waitWindow);
}
}

private function addFolder():void {
var file:File = new File();
file.browseForDirectory("Select a directory");
file.addEventListener(Event.SELECT, folderSelected);
}

private function folderSelected(evt:Event):void {
var file:File = evt.currentTarget as File;
Alert.show("Do you want to select subfolders too?", "Recursive selection?", Alert.YES | Alert.NO, null, warningClose);

function warningClose(ev:CloseEvent):void {
if (ev.detail == Alert.YES) {
startFolder(file, true);
}
if (ev.detail == Alert.NO) {
startFolder(file, false);
}
}
}

private function startFolder(file:File, recurs:Boolean):void {
PopUpManager.addPopUp(waitWindow, this);
PopUpManager.centerPopUp(waitWindow);
waitLabel.text = "Selecting folders...";
var timer:Timer = new Timer(100, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, onWait);
timer.start();
function onWait(ev:TimerEvent):void {
doFolder(file, recurs);
PopUpManager.removePopUp(waitWindow);
}
}

private function doFiles(files:Array):void {
for (var i:int = 0; i < files.length; i++) {
var alreadySelected:Boolean = false;
for (var u:int = 0; u < selectedFiles.length; u++) {
if (selectedFiles[u].type == 0 && selectedFiles[u].path == files[i].nativePath) {
alreadySelected = true;
}
}
if (!alreadySelected) selectedFiles.addItem({type:0, path:files[i].nativePath});
}
updateTotalFiles();
}

private function doFolder(file:File, isRecursive:Boolean):void {
var picturesInFolder:int = 0;
var childFiles:Array = file.getDirectoryListing();
for (var i:int = 0; i < childFiles.length; i++) {
if (childFiles[i].extension == "png" || childFiles[i].extension == "jpg" || childFiles[i].extension == "jpeg") {
picturesInFolder++;
}
if (childFiles[i].isDirectory && isRecursive) {
doFolder(childFiles[i], true);
}
}
if (picturesInFolder > 0) {
var alreadySelected:Boolean = false;
for (var a:int = 0; a < selectedFiles.length; a++) {
if (selectedFiles[a].type == 1 && selectedFiles[a].path == file.nativePath) {
alreadySelected = true;
}
}
if (!alreadySelected) selectedFiles.addItem( { type:1, path:file.nativePath, name:file.name, num:picturesInFolder } );
updateTotalFiles();
}
}

private function updateTotalFiles():void {
totalFiles = 0;
for (var i:int = 0; i < selectedFiles.length; i++) {
if (selectedFiles[i].type==1) {
totalFiles += selectedFiles[i].num;
}else {
totalFiles++;
}
}
labelSelected.text = totalFiles + " files selected";
}

private function removeSelected():void {
while (tileList.selectedItems.length > 0) {
selectedFiles.removeItemAt(tileList.selectedIndices[0]);
}
updateTotalFiles();
}

private function beginResize():void {
var canProceed:Boolean = true;
if (selectedFiles.length == 0) {
canProceed = false;
Alert.show("No files or folders are selected.", "Can't start resizing!");
}
if (nameInput.text.indexOf('%initialName%') < 0 && nameInput.text.indexOf('%num%') < 0) {
canProceed = false;
Alert.show("No wildcards were used in the name field! They are essential for each output file to have an unique name.", "Can't start resizing!");
}
if (newDestination.selected && destinationPicked == "") {
canProceed = false;
Alert.show("No destination set!", "Can't start resizing!");
}
var testName:String = nameInput.text.replace("%initialName%", "");
testName = testName.replace("%num%", "");
if(testName.indexOf('/')>=0 || testName.indexOf("\\")>=0 || testName.indexOf('?')>=0 || testName.indexOf('%')>=0 ||
testName.indexOf('*')>=0 || testName.indexOf(':')>=0 || testName.indexOf('|')>=0 || testName.indexOf('"')>=0 ||
testName.indexOf('<') >= 0 || testName.indexOf('>') >= 0 || testName.indexOf('.') >= 0) {
canProceed = false;
Alert.show("Illegal characters in the name field! Make sure file name field does not contain these characters: / \ ? % * : | \" < > . ", "Can't start resizing!");
}
if (canProceed) {
contentStack.selectedIndex = 3;
var timer:Timer = new Timer(400, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
timer.start();
function onTimer(evt:TimerEvent):void {
buttonError.enabled = false;
buttonReturn.enabled = false;
logArea.text = "";
logArea.appendText("Beginning resizing of " + totalFiles + " files...\n");
currentNum = 0;
totalErrors = 0;
successFiles = 0;
progressBar.setProgress(0, totalFiles);
errorText = "Error log of resizing operation of " + totalFiles + " files." + File.lineEnding + "Started at: " + new Date() + File.lineEnding;
errorText += "----------------------" + File.lineEnding;
statusText.setStyle("color", 0xffff55);
statusText.text = "IN PROGRESS";
nextAction();
}
}
}

private function resizeNext(isFolder:Boolean):void {
var file:File;
var canProceed:Boolean = true;
var loader:Loader;
var currentProgress:int = progressBar.value + 1;
progressBar.setProgress(currentProgress, totalFiles);

// if its a file
if (!isFolder){
currentNum++;
file = new File(selectedFiles[currentNum - 1].path);
}

// if its a folder
if (isFolder) {
file = folderArray.pop();
if (folderArray.length == 0) {
currentNum++;
}
}

if(file!=null) logArea.appendText("#" + currentProgress + " (" + file.name + ")\n");

// Error: file not found
if (file != null && !file.exists) {
canProceed = false;
appendHighlight("ERROR: File not found\n", 0xff2222);
errorText += "File not found at location: " + file.nativePath + File.lineEnding;
totalErrors++;
nextAction();
}


// Error: bad extension
if (file != null && (file.exists && file.extension.toLowerCase() != "png" && file.extension.toLowerCase() != "jpg" && file.extension.toLowerCase() != "jpeg")) {
canProceed = false;
appendHighlight("ERROR: Incorrect extension\n", 0xff2222);
errorText += "Incorrect file extension: " + file.nativePath + File.lineEnding;
totalErrors++;
nextAction();
}

if (file == null) { 
canProceed = false
nextAction();
};

var newExtension:String;
var currentW:int;
var currentH:int;
var newW:int;
var newH:int;
var ratio:Number;
var newName:String

var needResize:Boolean = true;

// load image
if (canProceed) {
loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE, onLoadComplete);
loader.load(new URLRequest(file.url));
}

// start timer
function onLoadComplete(evt:Event):void {
// new name
newName = nameInput.text.replace("%initialName%", file.name.substr(0, file.name.lastIndexOf('.')));
newName = newName.replace("%num%", currentProgress);

// new extension
if (formatCombo.selectedIndex == 0) newExtension = file.extension;
if (formatCombo.selectedIndex == 1) newExtension = "jpg";
if (formatCombo.selectedIndex == 2) newExtension = "png";

// new size
currentW = loader.content.width;
currentH = loader.content.height;

if (actionCombo.selectedIndex == 0) {
if (widthMeasure.selectedIndex == 0) newW = newWidth.value/100 * currentW;
if (widthMeasure.selectedIndex == 1) newW = newWidth.value;
if (heightMeasure.selectedIndex == 0) newH = newHeight.value/100 * currentH;
if (heightMeasure.selectedIndex == 1) newH = newHeight.value;
}

if (actionCombo.selectedIndex == 1) {
ratio = currentW / currentH;
if (widthMeasure.selectedIndex == 0) newW = newWidth.value/100 * currentW;
if (widthMeasure.selectedIndex == 1) newW = newWidth.value;
newH = newW / ratio;
}

if (actionCombo.selectedIndex == 2) {
ratio = currentW / currentH;
if (heightMeasure.selectedIndex == 0) newH = newHeight.value/100 * currentH;
if (heightMeasure.selectedIndex == 1) newH = newHeight.value;
newW = newH * ratio;
}

if (actionCombo.selectedIndex == 3) {
ratio = currentW / currentH;
newW = newWidth.value;
newH = newW / ratio;
if (newH > newHeight.value) {
newH = newHeight.value;
newW = ratio * newH;
}
}

if (skipCorrect.selected && currentW == newW && currentH == newH) {
needResize = false;
logArea.appendText("Skipped: " + file.name + "\n");
nextAction();
}

if(needResize){
var timer:Timer = new Timer(200, 1);
timer.addEventListener(TimerEvent.TIMER_COMPLETE, onTimer);
timer.start();
}
}

function onTimer(evt:TimerEvent):void {
// log
logArea.appendText("Resized from " + currentW + "x" + currentH + " to " + newW + "x" + newH + "\n");
logArea.appendText("Renamed to " + newName + "." + newExtension + "\n");

// extract
var matrix:Matrix = new Matrix();
matrix.scale(newW/currentW, newH/currentH);
var bitmapData:BitmapData = new BitmapData(newW, newH, false, 0xffffff);
bitmapData.draw(loader.content, matrix, null, null, null, true);

var encoder:IImageEncoder;
if (newExtension == "jpg") encoder = new JPEGEncoder(qualitySlider.value);
if (newExtension == "png") encoder = new PNGEncoder();
var byteArray:ByteArray = encoder.encode(bitmapData);

var newfile:File;
if (newDestination.selected) newfile = new File(destinationPicked + File.separator + newName + "." + newExtension);
if (oldDestination.selected) newfile = new File(file.parent.nativePath + File.separator + newName + "." + newExtension);
var stream:FileStream = new FileStream();
stream.open(newfile, FileMode.WRITE);
stream.writeBytes(byteArray);
stream.close();
successFiles++;

nextAction();
}

}

private function nextAction():void {
if (currentNum < selectedFiles.length) {
checkLineLimit();
var isFolder:Boolean = false;
var folder:File = new File(selectedFiles[currentNum].path);
// if its a folder, create a global array that stores all image files of the folder
if (folderArray.length > 0) isFolder = true;
if (folder.exists && selectedFiles.length > currentNum && selectedFiles[currentNum].type == 1 && folderArray.length == 0) {
isFolder = true;
folderArray = [];
var allContent:Array = folder.getDirectoryListing();
for (var i:int = 0; i < allContent.length; i++) {
if (allContent[i].isDirectory == false && (allContent[i].extension.toLowerCase() == "png" || allContent[i].extension.toLowerCase() == "jpg" || allContent[i].extension.toLowerCase() == "jpeg")) {
folderArray.push(allContent[i]);
}
}
if (selectedFiles[currentNum].num != folderArray.length) {
appendHighlight("ERROR: incorrect amount of image files found in folder " + folder.name + "\n", 0xff2222);
errorText += "Not enough or too many image files found in folder " + folder.nativePath + " (some files were removed/added before it was this folder's turn to be resized)" + File.lineEnding;
totalErrors++;
}
}
if (!folder.exists && selectedFiles.length > currentNum && selectedFiles[currentNum].type == 1 && folderArray.length == 0) {
appendHighlight("ERROR: folder not found (" + folder.name + ")\n", 0xff2222);
errorText += "Folder was not found at " + folder.nativePath + File.lineEnding;
totalErrors++;
currentNum++;
nextAction();
return;
}
resizeNext(isFolder);
}else {
progressBar.setProgress(totalFiles, totalFiles);
appendHighlight("Operation complete!\nErrors: " + totalErrors + "\nFiles resized: " + successFiles + "/" + totalFiles, 0xffff44);
errorText += "----------------------" + File.lineEnding + "Total errors: " + totalErrors + File.lineEnding + "Files resized: " + successFiles + "/" + totalFiles;
buttonError.enabled = true;
buttonReturn.enabled = true;
statusText.setStyle("color", 0x44ff44);
statusText.text = "COMPLETED";
}
}

private function pickDestination():void {
var file:File = new File();
file.browseForDirectory("Select ouput destination");
file.addEventListener(Event.SELECT, outputSelect);
function outputSelect(evt:Event):void {
destinationPicked = file.nativePath;
destinationInput.text = destinationPicked;
}
}

private function saveErrors():void {
var file:File = File.desktopDirectory.resolvePath("errors.txt");
file.browseForSave("Save error log");
file.addEventListener(Event.SELECT, errorSelect);

function errorSelect(evt:Event):void {
if (!file.nativePath.match(/^.*\.(txt)$/i)) {
file.nativePath += ".txt";
}
var fileStream:FileStream = new FileStream();
fileStream.open(file, FileMode.WRITE);
fileStream.writeUTFBytes(errorText);
fileStream.close();
}
}

private function appendHighlight(text:String, color:uint):void {
var formatHighlight:TextLayoutFormat = new TextLayoutFormat();
formatHighlight.color = color;
var formatNormal:TextLayoutFormat = new TextLayoutFormat();
formatNormal.color = 0xaaaaaa;
var firstAnchor:int = logArea.text.length;
logArea.appendText(text);
var secondAnchor:int = logArea.text.length;
logArea.setFormatOfRange(formatHighlight, firstAnchor, secondAnchor);
logArea.setFormatOfRange(formatNormal, secondAnchor, secondAnchor);
}

private function checkLineLimit():void {
if (countLines(logArea.text) > 24) {
logArea.text = "";
}
}

private function countLines(text:String):int {
var splittedText:Array = text.split("\n");
var lines:int = splittedText.length;
return lines;
}
]]>
</fx:Script>
   
<mx:ViewStack id="contentStack" width="100%" height="100%" >
<s:NavigatorContent width="100%" height="100%" hideEffect="fadeOut" showEffect="fadeIn">
<s:VGroup width="100%" height="100%" paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10">
<s:Label id="labelSelected">0 files selected</s:Label>
<mx:TileList id="tileList" width="282" height="100%" dataProvider="{selectedFiles}" itemRenderer="TileRenderer" columnWidth="60" rowHeight="60" columnCount="4" allowMultipleSelection="true" selectionColor="0xff0000" rollOverColor="0xff8888" />
<s:Button label="Add folder" width="100%" click="addFolder();" />
<s:Button label="Add files" width="100%" click="addFiles();" />
<s:Button label="{'Remove ' + tileList.selectedItems.length + ' items'}" width="100%" enabled="{tileList.selectedItems.length>0}" click="removeSelected();" />
<s:Button label="Continue" width="100%" click="contentStack.selectedIndex = 1;" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent width="100%" height="100%" hideEffect="fadeOut" showEffect="fadeIn">
<s:VGroup width="100%" height="100%" paddingLeft="10" paddingTop="10" paddingRight="10" paddingBottom="10">
<s:Button label="Return to file selection" width="100%" click="contentStack.selectedIndex = 0;" />

<s:Label>Resize options:</s:Label>

<mx:ComboBox width="100%" id="actionCombo" height="22" dataProvider="{actions}" selectedIndex="0" editable="false" change="actionChange();"
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>
<s:HGroup verticalAlign="middle">
<s:Label width="50">Width:</s:Label>
<s:NumericStepper id="newWidth" height="22" width="150" minimum="1" value="100" maximum="{(widthMeasure.selectedIndex==0)?(100):(4000)}" />
<mx:ComboBox id="widthMeasure" height="22" width="50" dataProvider="{measures}" selectedIndex="1" editable="false" 
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>
</s:HGroup>

<s:HGroup verticalAlign="middle">
<s:Label width="50">Height:</s:Label>
<s:NumericStepper id="newHeight" height="22" width="150" minimum="1" value="100" maximum="{(heightMeasure.selectedIndex==0)?(100):(4000)}"/>
<mx:ComboBox id="heightMeasure" height="22" width="50" dataProvider="{measures}" selectedIndex="1" editable="false" 
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>
</s:HGroup>

<s:CheckBox id="skipCorrect" label="Skip file if the size is correct" selected="false" />

<s:Label/>

<s:Label>Output file names:</s:Label>
<s:HGroup verticalAlign="middle">
<s:TextInput id="nameInput" width="240" text="%initialName%" />
<s:Button width="35" label="?" click="contentStack.selectedIndex = 2;" />
</s:HGroup>

<s:Label>Output destination:</s:Label>
<s:HGroup verticalAlign="middle">
<s:RadioButton id="oldDestination" label="Same directory" groupName="destinationGroup" selected="true" />
<s:RadioButton id="newDestination" label="Specified directory" groupName="destinationGroup" />
</s:HGroup>
<s:HGroup verticalAlign="middle" width="100%">
<s:TextInput width="100%" id="destinationInput" enabled="{newDestination.selected}" text="Select destination..." editable="false" />
<s:Button width="80" label="Browse" enabled="{newDestination.selected}" click="pickDestination();" />
</s:HGroup>

<s:Label/>

<s:Label>Output format:</s:Label>
<mx:ComboBox width="100%" height="22" id="formatCombo" dataProvider="{formats}" selectedIndex="0" editable="false" 
openEasingFunction="Linear.easeOut" closeEasingFunction="Linear.easeIn" openDuration="300" closeDuration="300"/>

<s:Label/>

<s:Label>Output JPG quality:</s:Label>
<s:HSlider id="qualitySlider" width="100%" minimum="1" maximum="100" value="100" />

<s:Label/>

<s:Button label="Resize" width="100%" click="beginResize();" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent width="100%" height="100%" hideEffect="fadeOut" showEffect="fadeIn">
<s:VGroup width="280" top="10" left="10">
<s:VGroup width="100%" height="410" gap="20">
<s:Label fontSize="20" width="100%" fontWeight="bold">Output file names</s:Label>
<s:Label width="100%">You can build names for output files using provided wildcards and combining them with text.</s:Label>
<s:Label width="100%">For example, "%initialName%_new" will generate names like "pic_new.jpg", "img_new.png", etc.</s:Label>
<s:Label width="100%">Available wildcards are:</s:Label>
<s:Label fontSize="18" width="100%" fontWeight="bold">%initialName%</s:Label>
<s:Label width="100%">Puts the initial name of the file that's being resized.</s:Label>
<s:Label fontSize="18" width="100%" fontWeight="bold">%num%</s:Label>
<s:Label width="100%">Gives each file a unique id number starting from 1.</s:Label>
</s:VGroup>
<s:Button label="Back" width="100%" click="contentStack.selectedIndex = 1;" />
</s:VGroup>
</s:NavigatorContent>
<s:NavigatorContent width="100%" height="100%" hideEffect="fadeOut" showEffect="fadeIn">
<s:VGroup width="280" height="440" top="10" left="10">
<s:Label width="100%" color="0xff3333" fontWeight="bold">Do not add, remove or rename selected files.</s:Label>
<s:Group width="100%">
<mx:ProgressBar id="progressBar" width="100%" mode="manual" label="Resizing %1 / %2" color="0xffffff" />
<s:Label top="20" left="200" textAlign="right" id="statusText" fontWeight="bold"/>
</s:Group>
<s:TextArea id="logArea" editable="false" width="100%" height="100%" />
<s:HGroup>
<s:Button id="buttonError" label="Save error log" click="saveErrors();" width="136" />
<s:Button id="buttonReturn" label="Return" click="contentStack.selectedIndex = 0;" width="136" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>
</mx:ViewStack>
</s:WindowedApplication>

Thanks for reading!

No comments:

Post a Comment