Artorius
Mitglied Plutonium
So, mein erster Versuch in Flex/Actionscript etwas in der Richtung Bildbearbeitung zu machen...
Über die GUI lassen sich fast alle Berechnungsgrößen einstellen, da ich mich selbst auf keine Vorgehensweise festlegen konnte/wollte.
Für das Dithering wurde http://code.google.com/p/imageditheringas3/ genutzt.
Einstiegspunkt
DicedImageProducer.as
Binaries und Sourcen sind angehängt. Falls ihr es starten wollt, benötigt ihr den FlashPlayer 10.
Ausserdem ist es den Flashplayer ja so ohne weiteres nicht erlaubt, auf die lokalen Dateien zuzugreifen, was aber benötigt wird, wenn ihr ein Bild auswählt.
Daher dann auf dieser Seite :http://www.macromedia.com/support/documentation/de/flashplayer/help/settings_manager04.html den Pfad zur .swf Datei eingestellen.
PS: Tut mir leid, dass er die Formatierung nicht komplett richtig übernommen hat. Hatte aber keine Lust dass jetzt alles nochmal nachzubearbeiten..

Über die GUI lassen sich fast alle Berechnungsgrößen einstellen, da ich mich selbst auf keine Vorgehensweise festlegen konnte/wollte.
Für das Dithering wurde http://code.google.com/p/imageditheringas3/ genutzt.
Einstiegspunkt
Code:
<?xml version="1.0" encoding="utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute">
<mx:Script>
<![CDATA[
import mx.controls.Alert;
import mx.managers.CursorManager;
import de.jw.mosaic.DicedImageProducer;
import de.jw.mosaic.data.DiceProducerConfig;
import mx.core.UIComponent;
import flash.net.FileReference;
[Bindable]
private var _imgDest:Bitmap = null;
private var _file:FileReference = null;
private var _config:DiceProducerConfig = new DiceProducerConfig();
private var _producer:DicedImageProducer = new DicedImageProducer();
public function set imgDestination(bitmap:Bitmap):void{
if(bitmap== null)
return;
comp_DestImage.width = bitmap.width;
comp_DestImage.height = bitmap.height;
comp_DestImage.scaleContent = false;
comp_Width.value = bitmap.width;
comp_Height.value = bitmap.height;
_imgDest = bitmap;
CursorManager.removeBusyCursor();
}
/**
*Start button clicked
*
*/
private function startBtnHandler(event:Event):void{
//read config values and save them
_config.colorSize = comp_Colors.value;
_config.dicesize = comp_DiceSize.value;
_config.dithType = comp_DitheringType.selectedItem["Type"] as String;
_config.sourcePixelsize = comp_PixelSize.value;
_config.convertToGreyScale = comp_Greyscale.selected;
_config.brightnessFunction = comp_BrightnessFunction.selectedItem["Function"] as String;
if(!_config.imgSource){
Alert.show("Keine Quelle angegeben","Fehler");
}
//Show busy cursor
CursorManager.setBusyCursor();
_producer.start(_config);
}
private function scaleBtnClickHandler(event:Event):void{
comp_DestImage.width = comp_Width.value;
comp_DestImage.height = comp_Height.value;
comp_DestImage.scaleContent = true;
comp_DestImage.validateNow();
}
/**
*Select button clicked. Show an file browser dialog
*/
private function selectImgBtnHandler(event:Event):void{
_file = new FileReference();
_file.addEventListener(Event.SELECT, selectHandler);
_file.browse();
}
/**
* File selected. Set as image source
*/
private function selectHandler(event:Event):void {
_file = FileReference(event.target);
_config.imgSource = _file;
comp_sourceURL.text = _file.name;
}
private function imgSizefocusHandler(event:Event):void{
comp_Height.value = comp_DestImage.height * (comp_Width.value / comp_DestImage.width);
}
]]>
</mx:Script>
<mx:HBox x="0" y="151">
<mx:Image id="comp_DestImage" source="{_imgDest}" scaleContent="false" smoothBitmapContent="true"/>
</mx:HBox>
<mx:Form x="10" y="0" width="373" height="82">
<mx:FormItem label="Würfelgröße" width="100%">
<mx:NumericStepper width="100%" id="comp_DiceSize" minimum="1" maximum="12" stepSize="1" toolTip="Größe der Würfel"/>
</mx:FormItem>
<mx:FormItem label="Pixelgröße" width="100%">
<mx:NumericStepper id="comp_PixelSize" value="1" minimum="1" maximum="24" stepSize="1" width="100%">
<mx:toolTip>Gibt an, wieviele Pixel (Anzahl x Anzahl) durch jeweils einen Würfel ersetzt werden</mx:toolTip>
</mx:NumericStepper>
</mx:FormItem>
</mx:Form>
<mx:Button x="10" y="90" label="start" click="startBtnHandler(event)"/>
<mx:Form x="391" y="0">
<mx:FormItem label="Dithering" width="100%">
<mx:ComboBox labelField="Label" width="100%" id="comp_DitheringType" toolTip="Dithering Verfahren auswählen" selectedIndex="0">
<mx:dataProvider>
<mx:ArrayCollection>
<mx:Object>
<mx:Type>ImageDitheringType.NO_DITHER</mx:Type>
<mx:Label>Kein Dithering</mx:Label>
</mx:Object>
<mx:Object>
<mx:Type>ImageDitheringType.FLOYD_STEINBERG</mx:Type>
<mx:Label>Floyd-Steinberg</mx:Label>
</mx:Object>
<mx:Object>
<mx:Type>ImageDitheringType.FALSE_FLOYD_STEINBER</mx:Type>
<mx:Label>'Falscher' Floyd-Steinberg</mx:Label>
</mx:Object>
<mx:Object>
<mx:Type>ImageDitheringType.STUCKI</mx:Type>
<mx:Label>Stucki</mx:Label>
</mx:Object>
</mx:ArrayCollection>
</mx:dataProvider>
</mx:ComboBox>
</mx:FormItem>
<mx:FormItem label="Farben" width="100%">
<mx:NumericStepper width="100%" id="comp_Colors" value="5" minimum="1" maximum="24" stepSize="1" toolTip="Anzahl der Farben pro Farbkanal"/>
</mx:FormItem>
</mx:Form>
<mx:Form x="660" y="0" height="82">
<mx:FormItem label="Schwarweiß" width="100%">
<mx:CheckBox id="comp_Greyscale">
<mx:toolTip>Kennung, ob das Bild vor dem Dithering in ein Schwarzweißbild umgewandelt werden soll</mx:toolTip>
</mx:CheckBox>
</mx:FormItem>
<mx:FormItem label="Helligkeitsfunktion" width="100%">
<mx:ComboBox width="100%" id="comp_BrightnessFunction" labelField="Label" selectedIndex="1">
<mx:ArrayCollection>
<mx:Object>
<mx:Function>brightnessFunction1</mx:Function>
<mx:Label>Maximum(rot, grün, blau)</mx:Label>
</mx:Object>
<mx:Object>
<mx:Function>brightnessFunction2</mx:Function>
<mx:Label>0.2126*rot + 0.7152*grün + 0.0722*blau</mx:Label>
</mx:Object>
<mx:Object>
<mx:Function>brightnessFunction3</mx:Function>
<mx:Label>SQRT(rot*rot + grün*grün + blau*blau))</mx:Label>
</mx:Object>
</mx:ArrayCollection>
</mx:ComboBox>
</mx:FormItem>
</mx:Form>
<mx:Button x="72" y="90" label="Bild skalieren" click="scaleBtnClickHandler(event)"/>
<mx:Form x="182" y="90" height="54" paddingBottom="0" paddingLeft="0" paddingRight="0" paddingTop="0">
<mx:FormItem label="Breite" width="100%">
<mx:NumericStepper id="comp_Width" minimum="1" maximum="6000" width="100%" focusOut="imgSizefocusHandler(event)"/>
</mx:FormItem>
<mx:FormItem label="Höhe" width="100%">
<mx:NumericStepper width="100%" id="comp_Height" minimum="1" value="1" maximum="6000" enabled="false"/>
</mx:FormItem>
</mx:Form>
<mx:TextInput x="313" y="90" width="339" id="comp_sourceURL" editable="false"/>
<mx:Button x="445" y="120" label="Quelle wählen" width="156" click="selectImgBtnHandler(event)"/>
</mx:Application>
DicedImageProducer.as
Code:
package de.jw.mosaic
{
import com.unitzeroone.fx.ImageDithering;
import de.jw.mosaic.data.DiceProducerConfig;
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.display.Loader;
import flash.display.LoaderInfo;
import flash.events.Event;
import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;
import flash.utils.ByteArray;
import mx.collections.ArrayCollection;
import mx.controls.Alert;
import mx.core.Application;
import mx.core.BitmapAsset;
public class DicedImageProducer
{
//Contains the loaded images
private var _imgList:Object = new Object();
//Embed the images and create references to them
[Embed(source="wuerfel1_12.png")]
public var dice1_12:Class;
[Embed(source="wuerfel2_12.png")]
public var dice2_12:Class;
[Embed(source="wuerfel3_12.png")]
public var dice3_12:Class;
[Embed(source="wuerfel4_12.png")]
public var dice4_12:Class;
[Embed(source="wuerfel5_12.png")]
public var dice5_12:Class;
[Embed(source="wuerfel6_12.png")]
public var dice6_12:Class;
private var _config:DiceProducerConfig = null;
public function DicedImageProducer():void{
}
/**
* Starts the image dicering. First load the image.
*
*@param config: dicing-Configuration
*/
public function start(config:DiceProducerConfig):void{
_config = config;
_config.imgSource.addEventListener(Event.COMPLETE,completeHandler);
_config.imgSource.load();
}
/**
*Image loaded.
*
*/
private function completeHandler(event:Event):void {
//remove the old listener function
_config.imgSource.removeEventListener(Event.COMPLETE,completeHandler);
//Now convert it into useable format
var loader:Loader = new Loader();
loader.contentLoaderInfo.addEventListener(Event.COMPLETE,imgLoaded);
loader.loadBytes(_config.imgSource.data as ByteArray);
}
/**
* Changing Image Data complete.
*
*/
private function imgLoaded(e:Event):void{
var data:BitmapData = ((e.currentTarget as LoaderInfo).content as Bitmap).bitmapData;
//create instances of the dices and scale them
var scaleFactor:Number = _config.dicesize / 12;
var matrix:Matrix = new Matrix();
matrix.scale(scaleFactor,scaleFactor);
var src:BitmapData = (new dice1_12() as BitmapAsset).bitmapData;
var dest:BitmapData = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["1"] = dest;
src = (new dice2_12() as BitmapAsset).bitmapData;
dest = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["2"] = dest;
src = (new dice2_12() as BitmapAsset).bitmapData;
dest = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["2"] = dest;
src = (new dice3_12() as BitmapAsset).bitmapData;
dest = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["3"] = dest;
src = (new dice4_12() as BitmapAsset).bitmapData;
dest = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["4"] = dest;
src = (new dice5_12() as BitmapAsset).bitmapData;
dest = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["5"] = dest;
src = (new dice6_12() as BitmapAsset).bitmapData;
dest = new BitmapData(_config.dicesize,_config.dicesize);
dest.draw(src,matrix);
_imgList["6"] = dest;
var width:int = data.width;
var height:int = data.height;
//Maximum pixel size in width or height is 8192 (flashplayer 10)
var px:Number = (width * _config.dicesize) / _config.sourcePixelsize;
var py:Number = (height * _config.dicesize) / _config.sourcePixelsize;
if(px > 8192){
Alert.show("Das resultierende Bild darf maximal 8192 Pixel breit sein","Fehler");
Application.application.imgDestination = null;
return;
}
if(py > 8192){
Alert.show("Das resultierende Bild darf maximal 8192 Pixel hoch sein","Fehler");
Application.application.imgDestination = null;
return;
}
//total number of pixels cannot exceed 16,777,216 pixels
if(px*py > 16777216){
Alert.show("Das resultierende Bild darf maximal 16,777,216 Pixel beinhalten","Fehler");
Application.application.imgDestination = null;
return;
}
ImageDithering.dither(data, _config.dithType, _config.colorSize,_config.convertToGreyScale);
//generate brightness List
var result:Object = generateBrightnessList(data,width,height,_config.sourcePixelsize);
//create the diced image
Application.application.imgDestination = createDiceImage(result["brightnessList"] as ArrayCollection,result["maxBrightness"] as Number,(width/_config.sourcePixelsize)*_config.dicesize,(height/_config.sourcePixelsize)*_config.dicesize);
}
/**
*Generat a list of brightness values.
*
*/
private function generateBrightnessList(data:BitmapData,imgWidth:int, imgHeight:int,sourcePixel:int):Object{
var brightnessList:ArrayCollection = new ArrayCollection();
var max:Number = 0;
var x:int =0;
var y:int =0;
var width:int =sourcePixel;
var height:int =sourcePixel;
var counter:String = "0";
while(y < data.height){
x = 0;
while(x < data.width){
//gets the pixel in a rectangle area
var array:ByteArray = data.getPixels(new Rectangle(x,y,width,height));
array.position = 0;
//respect the image width
width = Math.min(_config.dicesize,data.width-x);
var rgb:uint = 0;
var r:Number = 0;
var g:Number = 0;
var b:Number = 0;
var brightness:Number = 0;
//now calculate the average brightness of the rectangle
for(var i:int=0; i < array.bytesAvailable; i++){
rgb= array[i];
//calculate the red green and blue values
r = ((rgb >> 16) & 0xff)/255;
g = ((rgb >> 8) & 0xff)/255;
b = (rgb & 0xff);
//calls the brightness function
brightness+= this[_config.brightnessFunction].call(this,r,g,b);
}
brightness = brightness / array.length;
//Lets see if something went wrong
if(isNaN(brightness)){
brightness = 0;
}
//add value to the list
brightnessList.addItem(brightness);
//calculate the (temp) max brightness value
max = Math.max(max,brightness);
x+=sourcePixel;
}
height = Math.min(sourcePixel,data.height-y);
y+=sourcePixel;
}
//return a object, containing the brightness list and the max brightness value
return {brightnessList:brightnessList,maxBrightness:max};
}
//HSB color brightness
public function brightnessFunction1(r:Number,g:Number,b:Number):Number{
return Math.max(r, g, b);
}
//Generalised lightness, hue and saturation colormodel - brightness
public function brightnessFunction2(r:Number,g:Number,b:Number):Number{
return 0.2126*r + 0.7152*g + 0.0722*b;
}
//An other brightness function i found
public function brightnessFunction3(r:Number,g:Number,b:Number):Number{
return Math.sqrt(r*r + g*g + b*b);
}
/**
*Create a new image with dice-subimages based on an brightness list
*
*/
private function createDiceImage(brightnessList:ArrayCollection, maxBrightness:Number, imgWidth:int, imgHeight:int):Bitmap{
var y:int= 0;
var x:int = 0;
var tmp:Number = 0;
var tmp2:Number = 0;
var diceId:String = "";
//simple normalizer, so the values are between 0 and 1
var normalizer:Number = 1/maxBrightness;
var data:BitmapData = new BitmapData(imgWidth,imgHeight);
var i:int =0;
while(i < brightnessList.length){
tmp = brightnessList[i]*normalizer;
i++;
//we are using white dices with dark points
//-> hight brightness, low points
if(tmp > 0.83){
diceId = "1";
}
else if(tmp <= 0.83 && tmp >0.66){
diceId = "2";
}
else if(tmp <= 0.66 && tmp >0.5){
diceId = "3";
}
else if(tmp <= 0.5 && tmp >0.33){
diceId = "4";
}
else if(tmp <= 033 && tmp >0.16){
diceId = "5";
}
else{
diceId = "6";
}
//copy the dice source pixel into the result image
data.copyPixels(_imgList[diceId] as BitmapData,new Rectangle(0,0,_config.dicesize,_config.dicesize),new Point(x,y));
x+=_config.dicesize;
if(x>=imgWidth){
x= 0;
y+=_config.dicesize;
}
}
return new Bitmap(data);
}
}
}
Code:
package de.jw.mosaic.data
{
import flash.net.FileReference;
//A simple Container class
public class DiceProducerConfig
{
public var dicesize:int = 0;
public var sourcePixelsize:int = 0;
public var dithType:String = "";
public var colorSize:int = 0;
public var convertToGreyScale:Boolean = false;
public var brightnessFunction:String = null;
public var imgSource:FileReference = null;
public function DiceProducerConfig(){
}
}
}
Binaries und Sourcen sind angehängt. Falls ihr es starten wollt, benötigt ihr den FlashPlayer 10.
Ausserdem ist es den Flashplayer ja so ohne weiteres nicht erlaubt, auf die lokalen Dateien zuzugreifen, was aber benötigt wird, wenn ihr ein Bild auswählt.
Daher dann auf dieser Seite :http://www.macromedia.com/support/documentation/de/flashplayer/help/settings_manager04.html den Pfad zur .swf Datei eingestellen.
PS: Tut mir leid, dass er die Formatierung nicht komplett richtig übernommen hat. Hatte aber keine Lust dass jetzt alles nochmal nachzubearbeiten..
