Friday, February 17, 2012

Creating a Flex AIR Screenshot app: Part 27

In this tutorial we will fix an annoying issue with selecting the crop area and the left and bottom edges.

The problem right now is that if you draw a selection area, you can't move onto the left or bottom edges - the selection just stops right before them. On top of that, if we do selection to the far right - it is possible to go 1 pixel overboard.

The core of this issue is not entirely known, but I think it is an issue with the Box component which captures MOUSE_OVER and MOUSE_OUT events incorrectly. This may be due to the fact that the Box is contained within a Scroller container. The whole hit area seems to be 1 pixel off on both axes.

I tried many ways of fixing this issue, but have come to 1 solution. We will move the cursor "coordinates" according to the direction that the selection is drawed OR resized to. Because of this, we will also have to move the offset of the cursor image a little.

First, go to the drawMove() function and edit the first conditional content like this:

if (drawing) {
// bottom - right
if (evt.target.mouseX > drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = drawPreviousX-1;
drawingRect[1] = drawPreviousY;
drawingRect[2] = evt.target.mouseX - drawPreviousX+1;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
drawRectangle();
}
// bottom - left
if (evt.target.mouseX < drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = drawPreviousY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
drawRectangle();
}
// top - right
if (evt.target.mouseX > drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = drawPreviousX-1;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = evt.target.mouseX - drawPreviousX+1;
drawingRect[3] = drawPreviousY - evt.target.mouseY+1;
drawRectangle();
}
// top - left
if (evt.target.mouseX < drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = drawPreviousY - evt.target.mouseY+1;
drawRectangle();
}
}

You can see that I added +1 and -1 on certain lines. The reason behind them might seem complex. And damn, it is complex. The easiest way for me to determine where should I increase or decrease a pixel was the traditional trial-and-error method, where I just tried different combinations over and over until it worked. It was pretty time-consuming.

But we have to do the same with the 'resizing' conditional as well. Except that this time we have 8 if conditionals instead of 4.

if (resizing) {
if (resizeDirection == "downright" && evt.target.mouseX > drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = drawPreviousX;
drawingRect[1] = drawPreviousY;
drawingRect[2] = evt.target.mouseX - drawPreviousX;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
}
if (resizeDirection == "upleft" && evt.target.mouseX < drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = drawPreviousY - evt.target.mouseY;
}
if (resizeDirection == "downleft" && evt.target.mouseX < drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = drawPreviousY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
}
if (resizeDirection == "upright" && evt.target.mouseX > drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = drawPreviousX;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = evt.target.mouseX - drawPreviousX;
drawingRect[3] = drawPreviousY - evt.target.mouseY;
}
if (resizeDirection == "top" && evt.target.mouseY < drawPreviousY) {
drawingRect[1] = evt.target.mouseY;
drawingRect[3] = drawPreviousY - evt.target.mouseY;
}
if (resizeDirection == "bottom" && evt.target.mouseY > drawPreviousY) {
drawingRect[1] = drawPreviousY;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
}
if (resizeDirection == "right" && evt.target.mouseX > drawPreviousX) {
drawingRect[0] = drawPreviousX;
drawingRect[2] = evt.target.mouseX - drawPreviousX;
}
if (resizeDirection == "left" && evt.target.mouseX < drawPreviousX) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[2] = drawPreviousX - evt.target.mouseX;
}
drawRectangle();
}

The selection works flawlessly this way. The only problem that remains now is the cursor position - it is off by 1 pixel on the x axis. So, let's go everywhere we set the cursor graphic using setCursor method and change the third parameter from -9 to -10:

cursorManager.setCursor(cropCursor, 2, -10, -9);

Such place is the final conditional of drawMove():

if (!drawing && !dragging && !resizing){
cursorManager.removeAllCursors();
var lineClickArea:int = 8;
if (Math.abs(evt.target.mouseX - drawingRect[0])<lineClickArea && Math.abs(evt.target.mouseY - drawingRect[1])<lineClickArea) {
cursorManager.setCursor(resizeDiag2Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "upleft";
}else
if (Math.abs(evt.target.mouseX - (drawingRect[0]+drawingRect[2]))<lineClickArea && Math.abs(evt.target.mouseY - (drawingRect[1]+drawingRect[3]))<lineClickArea) {
cursorManager.setCursor(resizeDiag2Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "downright";
}else
if (Math.abs(evt.target.mouseX - drawingRect[0])<lineClickArea && Math.abs(evt.target.mouseY - (drawingRect[1]+drawingRect[3]))<lineClickArea) {
cursorManager.setCursor(resizeDiag1Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "downleft";
}else
if (Math.abs(evt.target.mouseX - (drawingRect[0]+drawingRect[2]))<lineClickArea && Math.abs(evt.target.mouseY - drawingRect[1])<lineClickArea) {
cursorManager.setCursor(resizeDiag1Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "upright";
}else
if (evt.target.mouseX > drawingRect[0] && evt.target.mouseX < drawingRect[0] + drawingRect[2] && Math.abs(evt.target.mouseY - drawingRect[1])<lineClickArea) {
cursorManager.setCursor(resizeVerticalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "top";
}else
if (evt.target.mouseX > drawingRect[0] && evt.target.mouseX < drawingRect[0] + drawingRect[2] && Math.abs(evt.target.mouseY - (drawingRect[1]+drawingRect[3]))<lineClickArea) {
cursorManager.setCursor(resizeVerticalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "bottom";
}else
if (evt.target.mouseY > drawingRect[1] && evt.target.mouseY < drawingRect[1] + drawingRect[3] && Math.abs(evt.target.mouseX - drawingRect[0])<lineClickArea) {
cursorManager.setCursor(resizeHorizontalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "left";
}else
if (evt.target.mouseY > drawingRect[1] && evt.target.mouseY < drawingRect[1] + drawingRect[3] && Math.abs(evt.target.mouseX - (drawingRect[0] + drawingRect[2]))<lineClickArea) {
cursorManager.setCursor(resizeHorizontalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "right";
}else
if (evt.target.mouseX > drawingRect[0] && evt.target.mouseX < drawingRect[0] + drawingRect[2] && evt.target.mouseY > drawingRect[1] && evt.target.mouseY < drawingRect[1] + drawingRect[3]) {
cursorManager.setCursor(dragCursor, 2, -10, -9);
inDragArea = true;
inResizeArea = false;
}else {
cursorManager.setCursor(cropCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = false;
}
}

As well as the drawOver() function:

private function drawOver(evt:MouseEvent):void {
if (dragging) {
cursorManager.setCursor(dragCursor, 2, -10, -9);
} else 
if (resizing) {
switch(resizeDirection) {
case "upleft":
case "downright":
cursorManager.setCursor(resizeDiag2Cursor, 2, -10, -9);
break;
case "upright":
case "downleft":
cursorManager.setCursor(resizeDiag1Cursor, 2, -10, -9);
break;
case "left":
case "right":
cursorManager.setCursor(resizeHorizontalCursor, 2, -10, -9);
break;
case "top":
case "bottom":
cursorManager.setCursor(resizeVerticalCursor, 2, -10, -9);
break;
}
} else {
cursorManager.setCursor(cropCursor, 2, -10, -9);
}
}

And now it finally works fine.

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:custom="*"
                       xmlns:mx="library://ns.adobe.com/flex/mx" showStatusBar="false"
   width="550" height="600" creationComplete="init();">
   
   
<fx:Declarations>
<mx:ArrayCollection id="headerTitles">
<fx:Object step="Step one:" description="load a web page." />
<fx:Object step="Loading..." description="please wait." />
<fx:Object step="Step two:" description="set your export preferences." />
<fx:Object step="Step two:" description="select the area you wish to crop." />
<fx:Object step="Step three:" description="set your export preferences for the cropped image." />
<fx:Object step="Exporting:" description="please wait." />
</mx:ArrayCollection>

<s:TitleWindow id="aboutWindow" width="500" height="160" close="closeAbout();" title="About Kirshot">
<s:HGroup>
<mx:Image source="@Embed('../bin/icons/Kirshot128.png')"/>
<s:Group width="372">
<s:VGroup top="10" left="10" right="10" bottom="10" width="100%">
<s:Label width="100%">Kirshot is a free open-source AIR application developed by Kirill Poletaev.</s:Label>
<s:Label width="100%">You can find a step-by-step tutorial series of making this program at kirill-poletaev.blogspot.com.</s:Label>
<s:Label width="100%">Contact me at lirx3m@hotmail.com.</s:Label>
<s:Label width="100%">I hope you enjoy this app and find it useful!</s:Label>
</s:VGroup>
</s:Group>
</s:HGroup>
</s:TitleWindow>
</fx:Declarations>

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

.descriptionText{
fontSize: 24;
color: #fff;
}

.descriptionText2{
fontSize: 16;
color: #fff;
}

.settingText{
fontSize: 16;
color: #fff;
}

#headStep{
fontSize: 30;
fontWeight: bold;
color: #ffffbb;
}

#headDesc{
fontSize: 30;
color: #ffffff;
}
</fx:Style>

<fx:Script>
<![CDATA[
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.filesystem.File;
import flash.filesystem.FileStream;
import flash.filters.GlowFilter;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.net.URLRequest;
import flash.utils.ByteArray;
import flash.utils.Timer;
import mx.controls.HTML;
import mx.core.FlexHTMLLoader;
import mx.events.FlexNativeWindowBoundsEvent;
import mx.controls.Alert;
import mx.events.ResizeEvent;
import mx.graphics.codec.IImageEncoder;
import mx.graphics.codec.JPEGEncoder;
import mx.graphics.codec.PNGEncoder;
import mx.graphics.ImageSnapshot;
import spark.components.Scroller;
import spark.primitives.BitmapImage;
import flash.filesystem.FileMode;
import mx.managers.PopUpManager;

[Bindable]
private var urlString:String;
private var tempHTML:HTML = new HTML();

private var preferences:SharedObject = SharedObject.getLocal("kirshotPreferences");
[Bindable]
private var pref_screensizes:Array;
[Bindable]
private var pref_format:String;
[Bindable]
private var pref_quality:int;
[Bindable]
private var pref_folder:Boolean;
[Bindable]
private var pref_destination:String;
[Bindable]
private var exportText:String;
private var screenGo:String;
private var drawing:Boolean = false;
private var dragging:Boolean = false;
private var resizing:Boolean = false;
private var inDragArea:Boolean = false;
private var inResizeArea:Boolean = false;
private var drawingRect:Array = [];
private var dragOffsetX:int = 0;
private var dragOffsetY:int = 0;
[Bindable]
private var drawPreviousX:int = 0;
[Bindable]
private var drawPreviousY:int = 0;
private var resizeDirection:String;
[Bindable]
private var canExportCrop:Boolean = false;

[Embed(source="../lib/cursor_crop.png")]
private var cropCursor:Class;

[Embed(source="../lib/cursor_move.png")]
private var dragCursor:Class;

[Embed(source="../lib/cursor_resize_vertical.png")]
private var resizeVerticalCursor:Class;
[Embed(source="../lib/cursor_resize_horizontal.png")]
private var resizeHorizontalCursor:Class;
[Embed(source="../lib/cursor_resize_diag1.png")]
private var resizeDiag1Cursor:Class;
[Embed(source="../lib/cursor_resize_diag2.png")]
private var resizeDiag2Cursor:Class;

private var screenSettings:Array;
private var cropBD:BitmapData;

private function init():void {
//preferences.data.firsttime = null;

// Set preferences if loaded for the first time
if (preferences.data.firsttime == null) {
preferences.data.firsttime = true;
preferences.data.screensizes = [ 
{ checked:true },
{ checked:true, w:1280, h:1024 },
{ checked:true, w:1280, h:800 },
{ checked:true, w:1024, h:768 },
{ checked:false, w:"", h:"" },
{ checked:false, w:"", h:"" },
{ checked:false, w:"", h:"" } ];
preferences.data.format = "JPG";
preferences.data.quality = 100;
preferences.data.folder = true;
preferences.data.destination = File.documentsDirectory.nativePath;
preferences.flush();
}

// Set preferences loaded from local storage
pref_screensizes = preferences.data.screensizes;
pref_format = preferences.data.format;
pref_quality = preferences.data.quality;
pref_folder = preferences.data.folder;
pref_destination = preferences.data.destination;

addElement(tempHTML);
removeElement(tempHTML);
}

private function doBrowse():void{
var file:File = new File();
file.addEventListener(Event.SELECT, browseSelect);
file.browseForOpen("Load a webpage");

function browseSelect(evt:Event):void {
urlInput.text = file.nativePath;
}
}

private function goScreenshot(screen:String):void {
screenGo = screen;
stack.selectedChild = screenshotloading;
urlString = urlInput.text;

addElement(tempHTML);
tempHTML.htmlLoader.useCache = false;
tempHTML.horizontalScrollPolicy = "off";
tempHTML.verticalScrollPolicy = "off";
tempHTML.visible = false;
tempHTML.addEventListener(Event.COMPLETE, onTempLoad);
tempHTML.htmlLoader.load(new URLRequest(urlString));
}

private function onTempLoad(evt:Event):void {
tempHTML.removeEventListener(Event.COMPLETE, onTempLoad);
if(screenGo=="screenshot"){
stack.selectedChild = screenshotsettings;
}
if (screenGo == "crop") {
if(tempHTML.contentWidth <= 4096 && tempHTML.contentHeight <= 4096){
var t:Timer = new Timer(1000, 1);
t.addEventListener(TimerEvent.TIMER_COMPLETE, tComplete);
t.start();
tempHTML.width = tempHTML.contentWidth;
tempHTML.height = tempHTML.contentHeight;
function tComplete(evt:TimerEvent):void {
stack.selectedChild = crop;
}
}else {
Alert.show("Dimensions of a screenshot cannot exceed 4096 pixels.", "Sorry...");
stack.selectedChild = loadpage;
}
}
}

private function cancelLoading():void {
tempHTML.removeEventListener(Event.COMPLETE, onTempLoad);
tempHTML.cancelLoad();
stack.selectedChild = loadpage;
removeElement(tempHTML);
}

private function screenshotBack():void {
saveScreenshotSettings();
stack.selectedChild = loadpage;
removeElement(tempHTML);
}

private function changeState():void {
if (stack.selectedChild == loadpage) {
contentBox.setStyle("horizontalAlign", "center");
urlInput.text = urlString;
tempHTML.width = 1;
tempHTML.height = 1;
tempHTML.removeEventListener(Event.COMPLETE, onTempLoad);
tempHTML.htmlLoader.loadString("<html></html>");
}
if (stack.selectedChild == crop) {
maximize();
contentBox.setStyle("horizontalAlign", "left");
var bd:BitmapData = new BitmapData(tempHTML.width, tempHTML.height, false);
bd.draw(tempHTML);
var b:Bitmap = new Bitmap(bd, "auto", true);
cropHTML.source = b;
drawArea.addEventListener(MouseEvent.MOUSE_DOWN, drawDown);
addEventListener(MouseEvent.MOUSE_UP, drawUp);
drawArea.addEventListener(MouseEvent.MOUSE_MOVE, drawMove);
displayWidth.filters = [new GlowFilter(0xffffff, 1, 3, 3, 50)];
displayHeight.filters = [new GlowFilter(0xffffff, 1, 3, 3, 50)];
drawArea.addEventListener(MouseEvent.MOUSE_OVER, drawOver);
drawArea.addEventListener(MouseEvent.MOUSE_OUT, drawOut);
drawingRect = [ -50, -50, 0, 0];
drawRectangle();
drawRectangle();
displayHeight.text = "";
displayWidth.text = "";
cropDraw.graphics.clear();
canExportCrop = false;
}
if (stack.selectedChild == screenshotsettings) {
screenSettings = [set2, set3, set4, set5, set6, set7];
contSize.text = "Full size (" + tempHTML.contentWidth + "x" + tempHTML.contentHeight + ")";
loadScreenshotSettings();
}
if (stack.selectedChild == cropsettings) {
contentBox.setStyle("horizontalAlign", "center");
loadCropSettings();
}
}

private function loadScreenshotSettings():void {
set1checkbox.selected = pref_screensizes[0].checked;

for (var i:int = 0; i < screenSettings.length; i++) {
screenSettings[i].checked = pref_screensizes[i + 1].checked;
screenSettings[i].w = pref_screensizes[i + 1].w;
screenSettings[i].h = pref_screensizes[i + 1].h;
}

if (pref_format == "JPG") {
screenRadioJPEG.selected = true;
} else {
screenRadioPNG.selected = true;
}
}

private function saveScreenshotSettings():void {
pref_screensizes[0].checked = set1checkbox.selected;

for (var i:int = 0; i < screenSettings.length; i++) {
pref_screensizes[i + 1].checked = screenSettings[i].checked;
pref_screensizes[i + 1].w = screenSettings[i].w;
pref_screensizes[i + 1].h = screenSettings[i].h;
}

if (screenRadioJPEG.selected == true) {
pref_format == "JPG";
} else {
pref_format == "PNG";
}

preferences.data.screensizes = pref_screensizes;
preferences.data.format = pref_format;
preferences.data.quality = pref_quality;
preferences.data.folder = pref_folder;
preferences.data.destination = pref_destination;
preferences.flush();
}

private function formatChange(newformat:String):void {
pref_format = newformat;
}

private function startExportScreenshot():void {
var canExport:Boolean = true;

for (var i:int = 0; i < screenSettings.length; i++) {
if (screenSettings[i].checked && ((screenSettings[i].w == "" || screenSettings[i].w == 0) || (screenSettings[i].h == "" || screenSettings[i].h == 0))) {
canExport = false;
}
}

if (canExport) {
if ((pref_folder && folderField.text != "") || !pref_folder) {
saveScreenshotSettings();
exportScreen();
}else {
Alert.show("Folder name should not be blank!", "Oops...");
}
}else {
Alert.show("One or more selected screen sizes are not entered or are invalid!", "Oops...");
}
}

private function screenshotDestination():void {
var newDestination:File = new File(pref_destination);
newDestination.browseForDirectory("Select directory");
newDestination.addEventListener(Event.SELECT, destinationSelect);

function destinationSelect(evt:Event):void {
pref_destination = newDestination.nativePath;
}
}

private function exportScreen():void {
var encoder:IImageEncoder;
var bd:BitmapData;
var byteArray:ByteArray;
var folderName:String = (pref_folder)?(folderField.text):("");
var fileName:String;
var file:File;
var fileStream:FileStream;
var screensToExport:Array = [];
stack.selectedChild = export;

if (pref_format == "JPG") {
encoder = new JPEGEncoder(pref_quality);
}
if (pref_format == "PNG") {
encoder = new PNGEncoder();
}

// add full-size screen to array if checked
if (pref_screensizes[0].checked) {
screensToExport = [ { w:tempHTML.contentWidth, h: tempHTML.contentHeight, full:true} ];
}

// add the rest screens to array if checked
for (var i:int = 0; i < screenSettings.length; i++) {
if (pref_screensizes[i + 1].checked) {
screensToExport.push( { w: pref_screensizes[i + 1].w, h:pref_screensizes[i + 1].h } );
}
}

// if nothing is checked, go to first page and stop code
if (screensToExport.length == 0) {
removeElement(tempHTML);
stack.selectedChild = loadpage;
return;
}

// create a timer that repeats itself as many times as many items there are in the array
var timer:Timer = new Timer(2000, screensToExport.length);
timer.addEventListener(TimerEvent.TIMER, onTimer);
// set sizes to the first size of the array
if (screensToExport[0].full) {
tempHTML.horizontalScrollPolicy = "off";
tempHTML.verticalScrollPolicy = "off";
}else {
tempHTML.horizontalScrollPolicy = "auto";
tempHTML.verticalScrollPolicy = "auto";
}
if(screensToExport[0].h <= 4096 && screensToExport[0].w <= 4096){
tempHTML.height = screensToExport[0].h;
tempHTML.width = screensToExport[0].w;
}
updateExportText(screensToExport[0].w, screensToExport[0].h, 1, screensToExport.length);
timer.start();

function onTimer(evt:TimerEvent):void {
// do export for the current size
if(screensToExport[timer.currentCount-1].h <= 4096 && screensToExport[timer.currentCount-1].w <= 4096){
timer.stop();
doExport();
timer.start();
}else {
Alert.show("Cannot export " + screensToExport[timer.currentCount-1].w + "x" + screensToExport[timer.currentCount-1].h + " - dimensions of a screenshot cannot extend 4096.","Sorry");
}
// change the size if this was not the last size in the array
if (timer.currentCount != screensToExport.length) {
tempHTML.horizontalScrollPolicy = "auto";
tempHTML.verticalScrollPolicy = "auto";
if(screensToExport[timer.currentCount].h <= 4096 && screensToExport[timer.currentCount].w <= 4096){
tempHTML.height = screensToExport[timer.currentCount].h;
tempHTML.width = screensToExport[timer.currentCount].w;
}
updateExportText(screensToExport[timer.currentCount].w, screensToExport[timer.currentCount].h, timer.currentCount+1, screensToExport.length);
}else {
// if it was the last size in the array, return to first page
timer.stop();
removeElement(tempHTML);
stack.selectedChild = loadpage;
}
}

function doExport():void {
bd = new BitmapData(tempHTML.width, tempHTML.height, false);
bd.draw(tempHTML, null, null, null, null, true);
byteArray = encoder.encode(bd);
fileName = pref_destination + File.separator + folderName + File.separator + tempHTML.width + "x" + tempHTML.height + "." + pref_format;
file = new File(fileName);
fileStream = new FileStream();
fileStream.open(file, FileMode.WRITE);
fileStream.writeBytes(byteArray);
fileStream.close();
}

function updateExportText(w:int, h:int, current:int, total:int):void {
exportText = "Exporting " + w + "x" + h + "." + pref_format + " (" + current + "/" + total + ")";
}
}

private function drawDown(evt:MouseEvent):void {
if(!inDragArea && !inResizeArea){
drawing = true;
drawingRect = [evt.target.mouseX, evt.target.mouseY, 1, 1];
drawPreviousX = evt.target.mouseX;
drawPreviousY = evt.target.mouseY;
drawRectangle();
}
if (inDragArea && !inResizeArea) {
dragging = true;
dragOffsetX = evt.target.mouseX - drawingRect[0];
dragOffsetY = evt.target.mouseY - drawingRect[1];
}
if (inResizeArea) {
resizing = true;
setResizingCoordinates();
}
}

private function drawUp(evt:MouseEvent):void{
drawing = false;
dragging = false;
resizing = false;
}

private function drawMove(evt:MouseEvent):void{
if (drawing) {
// bottom - right
if (evt.target.mouseX > drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = drawPreviousX-1;
drawingRect[1] = drawPreviousY;
drawingRect[2] = evt.target.mouseX - drawPreviousX+1;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
drawRectangle();
}
// bottom - left
if (evt.target.mouseX < drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = drawPreviousY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
drawRectangle();
}
// top - right
if (evt.target.mouseX > drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = drawPreviousX-1;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = evt.target.mouseX - drawPreviousX+1;
drawingRect[3] = drawPreviousY - evt.target.mouseY+1;
drawRectangle();
}
// top - left
if (evt.target.mouseX < drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = drawPreviousY - evt.target.mouseY+1;
drawRectangle();
}
}

if (dragging) {
var newX:int = evt.target.mouseX - dragOffsetX;
var newY:int = evt.target.mouseY - dragOffsetY;
drawingRect[0] = newX;
drawingRect[1] = newY;
validSelectionPositionCheck();
drawRectangle();
}

if (resizing) {
if (resizeDirection == "downright" && evt.target.mouseX > drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = drawPreviousX;
drawingRect[1] = drawPreviousY;
drawingRect[2] = evt.target.mouseX - drawPreviousX;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
}
if (resizeDirection == "upleft" && evt.target.mouseX < drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = drawPreviousY - evt.target.mouseY;
}
if (resizeDirection == "downleft" && evt.target.mouseX < drawPreviousX  && evt.target.mouseY > drawPreviousY) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[1] = drawPreviousY;
drawingRect[2] = drawPreviousX - evt.target.mouseX+1;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
}
if (resizeDirection == "upright" && evt.target.mouseX > drawPreviousX  && evt.target.mouseY < drawPreviousY) {
drawingRect[0] = drawPreviousX;
drawingRect[1] = evt.target.mouseY;
drawingRect[2] = evt.target.mouseX - drawPreviousX;
drawingRect[3] = drawPreviousY - evt.target.mouseY;
}
if (resizeDirection == "top" && evt.target.mouseY < drawPreviousY) {
drawingRect[1] = evt.target.mouseY;
drawingRect[3] = drawPreviousY - evt.target.mouseY;
}
if (resizeDirection == "bottom" && evt.target.mouseY > drawPreviousY) {
drawingRect[1] = drawPreviousY;
drawingRect[3] = evt.target.mouseY - drawPreviousY+1;
}
if (resizeDirection == "right" && evt.target.mouseX > drawPreviousX) {
drawingRect[0] = drawPreviousX;
drawingRect[2] = evt.target.mouseX - drawPreviousX;
}
if (resizeDirection == "left" && evt.target.mouseX < drawPreviousX) {
drawingRect[0] = evt.target.mouseX-1;
drawingRect[2] = drawPreviousX - evt.target.mouseX;
}
drawRectangle();
}

if (!drawing && !dragging && !resizing){
cursorManager.removeAllCursors();
var lineClickArea:int = 8;
if (Math.abs(evt.target.mouseX - drawingRect[0])<lineClickArea && Math.abs(evt.target.mouseY - drawingRect[1])<lineClickArea) {
cursorManager.setCursor(resizeDiag2Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "upleft";
}else
if (Math.abs(evt.target.mouseX - (drawingRect[0]+drawingRect[2]))<lineClickArea && Math.abs(evt.target.mouseY - (drawingRect[1]+drawingRect[3]))<lineClickArea) {
cursorManager.setCursor(resizeDiag2Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "downright";
}else
if (Math.abs(evt.target.mouseX - drawingRect[0])<lineClickArea && Math.abs(evt.target.mouseY - (drawingRect[1]+drawingRect[3]))<lineClickArea) {
cursorManager.setCursor(resizeDiag1Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "downleft";
}else
if (Math.abs(evt.target.mouseX - (drawingRect[0]+drawingRect[2]))<lineClickArea && Math.abs(evt.target.mouseY - drawingRect[1])<lineClickArea) {
cursorManager.setCursor(resizeDiag1Cursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "upright";
}else
if (evt.target.mouseX > drawingRect[0] && evt.target.mouseX < drawingRect[0] + drawingRect[2] && Math.abs(evt.target.mouseY - drawingRect[1])<lineClickArea) {
cursorManager.setCursor(resizeVerticalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "top";
}else
if (evt.target.mouseX > drawingRect[0] && evt.target.mouseX < drawingRect[0] + drawingRect[2] && Math.abs(evt.target.mouseY - (drawingRect[1]+drawingRect[3]))<lineClickArea) {
cursorManager.setCursor(resizeVerticalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "bottom";
}else
if (evt.target.mouseY > drawingRect[1] && evt.target.mouseY < drawingRect[1] + drawingRect[3] && Math.abs(evt.target.mouseX - drawingRect[0])<lineClickArea) {
cursorManager.setCursor(resizeHorizontalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "left";
}else
if (evt.target.mouseY > drawingRect[1] && evt.target.mouseY < drawingRect[1] + drawingRect[3] && Math.abs(evt.target.mouseX - (drawingRect[0] + drawingRect[2]))<lineClickArea) {
cursorManager.setCursor(resizeHorizontalCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = true;
resizeDirection = "right";
}else
if (evt.target.mouseX > drawingRect[0] && evt.target.mouseX < drawingRect[0] + drawingRect[2] && evt.target.mouseY > drawingRect[1] && evt.target.mouseY < drawingRect[1] + drawingRect[3]) {
cursorManager.setCursor(dragCursor, 2, -10, -9);
inDragArea = true;
inResizeArea = false;
}else {
cursorManager.setCursor(cropCursor, 2, -10, -9);
inDragArea = false;
inResizeArea = false;
}
}
}

private function setResizingCoordinates():void {
if (resizeDirection == "downright") {
drawPreviousX = drawingRect[0];
drawPreviousY = drawingRect[1];
}else
if (resizeDirection == "upleft") {
drawPreviousX = drawingRect[0] + drawingRect[2];
drawPreviousY = drawingRect[1] + drawingRect[3];
}else
if (resizeDirection == "downleft") {
drawPreviousX = drawingRect[0] + drawingRect[2];
drawPreviousY = drawingRect[1];
}else
if (resizeDirection == "upright") {
drawPreviousX = drawingRect[0];
drawPreviousY = drawingRect[1] + drawingRect[3];
}else
if (resizeDirection == "top") {
drawPreviousY = drawingRect[1] + drawingRect[3];
}else
if (resizeDirection == "bottom") {
drawPreviousY = drawingRect[1];
}else
if (resizeDirection == "left") {
drawPreviousX = drawingRect[0] + drawingRect[2];
}else
if (resizeDirection == "right") {
drawPreviousX = drawingRect[0];
}
}

private function drawRectangle():void {
displayWidth.x = drawingRect[0];
if (displayWidth.x + displayWidth.width > cropDraw.width) {
displayWidth.x -= displayWidth.width;
}
displayWidth.y = drawingRect[1] - 12;
if (displayWidth.y < 0) {
displayWidth.y += displayWidth.height;
}
displayWidth.text = Math.abs(drawingRect[2]) + "px";
displayHeight.x = drawingRect[0] + drawingRect[2] + 4;
if (displayHeight.x + displayHeight.width > cropDraw.width) {
displayHeight.x -= (displayHeight.width + 4);
}
displayHeight.y = drawingRect[1] + drawingRect[3] - 12;
if (displayHeight.y < 0) {
displayHeight.y += displayHeight.height;
}
displayHeight.text = Math.abs(drawingRect[3]) + "px";
cropDraw.graphics.clear();
cropDraw.graphics.lineStyle(1, 0xff0000);
cropDraw.graphics.beginFill(0x00ff00, 0.15);
cropDraw.graphics.drawRect(drawingRect[0], drawingRect[1], drawingRect[2]-1, drawingRect[3]-1);
cropDraw.graphics.endFill();
if (drawingRect[2] > 1 && drawingRect[3] > 1) {
canExportCrop = true;
}else {
canExportCrop = false;
}
}

private function drawOver(evt:MouseEvent):void {
if (dragging) {
cursorManager.setCursor(dragCursor, 2, -10, -9);
} else 
if (resizing) {
switch(resizeDirection) {
case "upleft":
case "downright":
cursorManager.setCursor(resizeDiag2Cursor, 2, -10, -9);
break;
case "upright":
case "downleft":
cursorManager.setCursor(resizeDiag1Cursor, 2, -10, -9);
break;
case "left":
case "right":
cursorManager.setCursor(resizeHorizontalCursor, 2, -10, -9);
break;
case "top":
case "bottom":
cursorManager.setCursor(resizeVerticalCursor, 2, -10, -9);
break;
}
} else {
cursorManager.setCursor(cropCursor, 2, -10, -9);
}
}

private function drawOut(evt:MouseEvent):void {
cursorManager.removeAllCursors();
}

private function validSelectionPositionCheck():void {
if (drawingRect[0] < 0) {
drawingRect[0] = 0;
}
if (drawingRect[0] > drawArea.width - drawingRect[2]) {
drawingRect[0] = drawArea.width - drawingRect[2];
}
if (drawingRect[1] < 0) {
drawingRect[1] = 0;
}
if (drawingRect[1] > drawArea.height - drawingRect[3]) {
drawingRect[1] = drawArea.height - drawingRect[3];
}
}

private function doExportCrop():void {
cropBD = new BitmapData(drawingRect[2], drawingRect[3], false);
cropBD.draw(cropHTML, new Matrix(1, 0, 0, 1, -drawingRect[0], -drawingRect[1]));
stack.selectedChild = cropsettings;
}

private function loadCropSettings():void {
if (pref_format == "JPG") {
cropRadioJPEG.selected = true;
} else {
cropRadioPNG.selected = true;
}
}

private function cropBack():void {
saveCropSettings();
stack.selectedChild = loadpage;
removeElement(tempHTML);
}

private function saveCropSettings():void {
preferences.data.format = pref_format;
preferences.data.quality = pref_quality;
preferences.flush();
}

private function startExportCrop():void {
var encoder:IImageEncoder;
var byteArray:ByteArray;
var file:File;
var fileStream:FileStream;

if (pref_format == "JPG") {
encoder = new JPEGEncoder(pref_quality);
}
if (pref_format == "PNG") {
encoder = new PNGEncoder();
}

byteArray = encoder.encode(cropBD);
file = File.documentsDirectory.resolvePath("crop");
file.browseForSave("Save cropped image");
file.addEventListener(Event.SELECT, saveSelect);

function saveSelect():void {
file.nativePath += "." + pref_format;
fileStream = new FileStream();
fileStream.open(file, FileMode.WRITE);
fileStream.writeBytes(byteArray);
fileStream.close();
cropBack();
}
}

private function openAbout():void{
PopUpManager.addPopUp(aboutWindow, this, true);
PopUpManager.centerPopUp(aboutWindow);
}

private function closeAbout():void{
PopUpManager.removePopUp(aboutWindow);
}

private function cropSelectAll():void{
drawingRect[0] = 0;
drawingRect[1] = 0;
drawingRect[2] = drawArea.width;
drawingRect[3] = drawArea.height;
displayHeight.width = 40;
displayWidth.width = 40;
drawRectangle();
}
]]>
</fx:Script>
   
<s:VGroup width="100%" height="100%" gap="0">
<mx:HBox backgroundColor="#333333" height="46" width="100%" paddingTop="10" paddingLeft="10">
<s:Label id="headStep" text="{headerTitles.getItemAt(stack.selectedIndex).step}" />
<s:Label id="headDesc" text="{headerTitles.getItemAt(stack.selectedIndex).description}" />
</mx:HBox>
<mx:Box backgroundColor="#666666" width="100%" height="100%" id="contentBox" horizontalAlign="center">
<mx:ViewStack id="stack" change="changeState();">
<s:NavigatorContent id="loadpage">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText">Enter the link to the page:</s:Label>
<s:HGroup>
<s:TextInput width="250" id="urlInput" text="http://" />
<s:Button label="Browse local..." click="doBrowse();" />
<s:Button label="?" click="openAbout();" width="30" />
</s:HGroup>
<s:HGroup>
<custom:ImageButton img="@Embed('../lib/b_screenshot.png')" over="@Embed('../lib/b_screenshot_over.png')" toolTip="Take screenshots" click="goScreenshot('screenshot');" buttonMode="true" enabled="{urlInput.text!=''}" />
<custom:ImageButton img="@Embed('../lib/b_cut.png')" over="@Embed('../lib/b_cut_over.png')" toolTip="Crop area" click="goScreenshot('crop');" buttonMode="true" enabled="{urlInput.text!=''}" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="screenshotloading">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText">The page is being loaded...</s:Label>
<s:Button label="Cancel" click="cancelLoading();" />
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="screenshotsettings">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText2">Select screenshot screen sizes:</s:Label>
<s:SkinnableContainer backgroundColor="#999999" width="310" height="18" >
<s:CheckBox toolTip="Use this screen size" x="4" id="set1checkbox" />
<s:Label id="contSize" styleName="settingText" x="22" y="3" />
</s:SkinnableContainer>
<custom:ScreenSetting id="set2" />
<custom:ScreenSetting id="set3" />
<custom:ScreenSetting id="set4" />
<custom:ScreenSetting id="set5" />
<custom:ScreenSetting id="set6" />
<custom:ScreenSetting id="set7" />

<s:Label/>

<s:Label styleName="descriptionText2">Export as:</s:Label>
<s:HGroup>
<s:RadioButton id="screenRadioJPEG" label="JPEG" groupName="screenshotFormat" change="formatChange('JPEG');" styleName="descriptionText2" /> 
<s:RadioButton id="screenRadioPNG" label="PNG" groupName="screenshotFormat" change="formatChange('PNG');" styleName="descriptionText2" /> 
</s:HGroup>
<s:Label styleName="descriptionText2">Quality:</s:Label>
<s:HSlider id="screenQualitySlider" width="310" minimum="1" maximum="100" liveDragging="true" enabled="{pref_format=='JPEG'}" value="@{pref_quality}" />

<s:Label/>

<s:Label styleName="descriptionText2">Export destination:</s:Label>
<s:HGroup width="310">
<s:TextInput editable="false" width="100%" toolTip="Destination" text="{pref_destination}" />
<s:Button label="Browse" click="screenshotDestination();" />
</s:HGroup>
<s:CheckBox id="folderCheckbox" label="Create new folder with exported images" styleName="descriptionText2" selected="@{pref_folder}" />
<s:TextInput id="folderField" width="100%" toolTip="Folder name" maxChars="200" enabled="{folderCheckbox.selected}" restrict="a-zA-Z0-9._-=+" />
<s:HGroup>
<s:Button label="Back" click="screenshotBack();" />
<s:Button label="Export" click="startExportScreenshot();" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="crop">
<s:VGroup width="{contentBox.width}" height="100%" gap="0" horizontalAlign="center">
<mx:HBox backgroundColor="#999999" width="100%" height="24" verticalScrollPolicy="off" verticalAlign="middle" paddingLeft="2">
<s:Button label="Back" click="{stack.selectedChild = loadpage;}" />
<s:Button label="Select all" click="cropSelectAll();" />
<s:Button label="Export selection" enabled="{canExportCrop}" click="doExportCrop();" />

</mx:HBox>
<s:Scroller width="{(contentBox.width>cropHTML.width+15)?(cropHTML.width+15):(contentBox.width)}" height="{contentBox.height-24}" id="scrollHTML" mouseEnabled="false">
<s:Group>
<mx:Image id="cropHTML" />
<s:SpriteVisualElement id="cropDraw" width="{cropHTML.width}" height="{cropHTML.height}" />
<mx:Box width="{cropHTML.width}" height="{cropHTML.height}" alpha="0" id="drawArea" backgroundColor="#000000" />
<s:Label id="displayWidth" color="#000000" mouseEnabled="false" />
<s:Label id="displayHeight" color="#000000" mouseEnabled="false" />
</s:Group>
</s:Scroller>
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="cropsettings">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText2">Export as:</s:Label>
<s:HGroup>
<s:RadioButton id="cropRadioJPEG" label="JPEG" groupName="screenshotFormat" change="formatChange('JPEG');" styleName="descriptionText2" /> 
<s:RadioButton id="cropRadioPNG" label="PNG" groupName="screenshotFormat" change="formatChange('PNG');" styleName="descriptionText2" /> 
</s:HGroup>
<s:Label styleName="descriptionText2">Quality:</s:Label>
<s:HSlider id="cropQualitySlider" width="310" minimum="1" maximum="100" liveDragging="true" enabled="{pref_format=='JPEG'}" value="@{pref_quality}" />

<s:Label/>

<s:HGroup>
<s:Button label="Back" click="cropBack();" />
<s:Button label="Export" click="startExportCrop();" />
</s:HGroup>
</s:VGroup>
</s:NavigatorContent>

<s:NavigatorContent id="export">
<s:VGroup width="100%" horizontalAlign="center" paddingTop="20">
<s:Label styleName="descriptionText" text="{exportText}" />
</s:VGroup>
</s:NavigatorContent>
</mx:ViewStack>
</mx:Box>
</s:VGroup>

</s:WindowedApplication>

Thanks for reading!

No comments:

Post a Comment