09 de Septiembre de 2010

Notas Espacio Programación

galería de imágenesvideo
AUTOR: Andrés Fernández
FECHA: 11/2/2009
LECTURAS:1324
Buscar Notas
volver
Experimento con BitmapData

Corregir Ojos Rojos con BitmapData

Una clase que nos permitirá modificar zonas de colores semejantes utilizando la clase BitmapData
Basada en lo aprendido en estos 2 artículos:
-BitmapData Colour Pallette
-Export JPEG with Flash/PHP
mostraremos una clase que nos permitirá generar una aplicación para corregir el efecto ojos rojos en una fotografía, como la que vemos a continuación:



Como puede observarse, al hacer click en un punto de la imagen, los puntos restantes dentro de la circunferencia de selección, que posean un color similar al del primer punto mencionado, se convierten a escala de grises.

Para cocinar esto, necesitaremos 3 ingredientes:


1-Similitud de colores

Para determinar la similitud entre 2 colores dados, el tip que nos indican en el primero de los 2 artículos mencionados al principio de esta nota, es sumar los cuadrados de las diferencias de los componentes rojo, verde y azul de cada uno de esos colores, y compararlos con una variable llamada tolerancia:
public static function similarcolour1:uintcolour2:uinttolerance:Number 0.01 ):Boolean
{
    var 
RGB1:Object Hex24ToRGBcolour1 );
    var 
RGB2:Object Hex24ToRGBcolour2 );
 
    
tolerance tolerance * ( 255 255 ) << 0;
 
    var 
distance:Number 0;
 
    
distance += Math.powRGB1.red RGB2.red);
    
distance += Math.powRGB1.green RGB2.green);
    
distance += Math.powRGB1.blue RGB2.blue);
 
    return 
distance <= tolerance;
}



2-Obtener el color de cualquier pixel de nuestro mapa de bits
Esto nos lo enseñan en el segundo de los enlaces, donde nos proporcionan el siguiente ejemplo, que muestra cómo escribir en un campo de texto los valores rgb del pixel donde se encuentra el puntero del mouse, utilizando el método getPixel de la clase BitmapData:


import flash.display.*

var 
bmp:BitmapData = new BitmapData(this._widththis._heightfalse)
bmp.draw(this);

this.onMouseMove = function(){
    var 
pColor:Number bmp.getPixel(_xmouse_ymouse)
    var 
hexColor:String pColor.toString(16).toUpperCase()
    while(
hexColor.length 6){
        
hexColor "0" hexColor
    
}
    var 
Number("0x" hexColor.substr(0,2))
    var 
Number("0x" hexColor.substr(2,2))
    var 
Number("0x" hexColor.substr(4,2))
    
testo.text "0x" hexColor ", {r:" ", g:" ", b:" "}"
}


3-Pasar a escala de grises
Esto lo vimos en una nota anterior, donde usábamos la siguiente función:
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.geom.Point;
function 
BMP2GrayScale(holder_mc){
    
myBitmap = new BitmapData(holder_mc._widthholder_mc._height,true,0x00FFFFFF);
    
= new Matrix();
    
m.scale(1,1);
    
m.translate(0,0);
    
myBitmap.draw(holder_mc,m);
    
attachBitmap(myBitmap,1);
    var 
rect:Rectangle = new Rectangle(00myBitmap.widthmyBitmap.height);
    var 
bmpTem=new BitmapData(myBitmap.widthmyBitmap.height,true,0x00FFFFFF);
    
bmpTem.copyPixels(myBitmap,new Rectangle(00myBitmap.widthmyBitmap.height),new Point(0,0));
    
bmpTem.copyChannel(bmpTemrect, new Point(00), 21);
    
bmpTem.copyChannel(bmpTemrect, new Point(00), 22);
    
bmpTem.copyChannel(bmpTemrect, new Point(00), 24);
    
myBitmap.copyPixels(bmpTem,new Rectangle(00myBitmap.widthmyBitmap.height),new Point(0,0));
    
bmpTem.dispose();    
}
this.createEmptyMovieClip("holder_mc",this.getNextHighestDepth());
var 
loader = new MovieClipLoader();
loader.addListener(this);
loader.loadClip("http://www.disegnocentell.com.ar/ejemplos/img/1.jpg",holder_mc);
function 
onLoadInit(tm){
    
BMP2GrayScale(tm);
}



Todas esas cosas están mezcladas dentro de nuestra clase, cuyo código es el siguiente:
import flash.display.BitmapData;
import flash.geom.Matrix;
import flash.geom.Rectangle;
import flash.geom.Point;
class 
ojorojo {
    public var 
addListener:Function;
    public var 
broadcastMessage:Function;
    public var 
myBitmap:BitmapData ;
    public var 
record:Object ;
    public function 
ojorojo(){
        
AsBroadcaster.initialize(this);
    }
    public function 
cargarImagen(scope:MovieClip,jpg:String){
        var 
_this:Object=this;
        var 
holder_mc:MovieClip=scope.createEmptyMovieClip("holder_mc",scope.getNextHighestDepth());
        
_this.broadcastMessage("onStart"holder_mc);
        
updateAfterEvent();
        var 
loader:MovieClipLoader = new MovieClipLoader();
        
loader.addListener(scope);
        
loader.loadClip(jpg,holder_mc);
        
scope.onLoadInit=function(){
            
_this.broadcastMessage("onInit"holder_mc);
            
_this.myBitmap = new BitmapData(holder_mc._widthholder_mc._height,true,0x00FFFFFF);
            var 
m:Matrix = new Matrix();
            
m.scale(1,1)
            
m.translate(0,0)
            
_this.myBitmap.draw(holder_mc,m)
            
scope.attachBitmap(_this.myBitmap,scope.getNextHighestDepth());
            var 
pp:MovieClip=scope.createEmptyMovieClip('cuadro',scope.getNextHighestDepth());
            var 
R:Number=10;
            
with(pp){
                
lineStyle(1,0xFFFFFF,100);
                
moveTo((R*Math.sin(0)),(R*Math.cos(0)));
                for(
i=0;i<(2*Math.PI);i+=(2*Math.PI)/360){
                    
lineTo((R*Math.sin(i)),(R*Math.cos(i)));
                }
                
moveTo(-(R/1.2)-R,0);
                
lineTo((R/1.2)-R,0)
        
                
moveTo(-(R/1.2)+R,0);
                
lineTo((R/1.2)+R,0)
        
                
moveTo(0,-(R/1.2)+R);
                
lineTo(0,(R/1.2)+R)
        
                
moveTo(0,-(R/1.2)-R);
                
lineTo(0,(R/1.2)-R)
        
            }
            
holder_mc.onMouseMove=function(){
                if(
this._ymouse>this._height){
                    
pp._visible=false;
                    
Mouse.show();
                }else{
                    
pp._visible=true;
                    
Mouse.hide();
                }
                
pp._x=this._xmouse;
                
pp._y=this._ymouse;
            }
            
holder_mc.onPress=function(){
                var 
pixelColor:Number=_this.myBitmap.getPixel(this._xmouse,this._ymouse);
                
_this.broadcastMessage("onSelectedColor"holder_mc,pixelColor);
                
_this.cambiar (pixelColor,20,20,this._xmouse,this._ymouse);
            }
            
        }
        
scope.onLoadError=function(){
            
_this.broadcastMessage("onError"holder_mc);
        }
        
    }
    private function 
argbtohex(a:Numberr:Numberg:Numberb:Number){
        return (
a<<24 r<<16 g<<b);
    }
    private function 
hextoargb(val:Number){
        var 
col:Object={}
        
col.alpha = (val >> 24) & 0xFF
        col
.red = (val >> 16) & 0xFF
        col
.green = (val >> 8) & 0xFF
        col
.blue val 0xFF
        
return col
    
}
    private function 
cambiar (color,ancho,alto,Px,Py){
        var 
_this:Object=this;
        var 
inicioX:Number=Px-(ancho/2);
        var 
inicioY:Number=Py-(alto/2);
        var 
finX:Number=Px+(ancho/2);
        var 
finY:Number=Py+(alto/2);
        var 
i:Number;
        var 
j:Number;
        for(
i=inicioX;i<finX;i++){
            for(
j=inicioY;j<finY;j++){
                var 
pixelColor:Number=_this.myBitmap.getPixel(i,j);
                var 
centro:Point=new Point(Px,Py);
                var 
aevaluar:Point=new Point(i,j);
                if(
_this.similarpixelColorcolor0.05 ) && (Point.distance(centro,aevaluar)<=10)){
                    var 
rect:Rectangle = new Rectangle(0011);
                    var 
bmpTem:BitmapData=new BitmapData(11,true,0x00FFFFFF);
                    
bmpTem.copyPixels(_this.myBitmap,new Rectangle(ij11),new Point(0,0));
                    
bmpTem.copyChannel(bmpTemrect, new Point(00), 21);
                    
bmpTem.copyChannel(bmpTemrect, new Point(00), 22);
                    
bmpTem.copyChannel(bmpTemrect, new Point(00), 24);
                    
_this.myBitmap.copyPixels(bmpTem,new Rectangle(0011),new Point(i,j));
                    
bmpTem.dispose();
                }
            }
        }
        
_this.broadcastMessage("onChangeZone"Px,Py);
    }
    private function 
similarcolour1colour2tolerance ){
        var 
_this:Object=this;
        var 
RGB1:Object _this.hextoargbcolour1 );
        var 
RGB2:Object _this.hextoargbcolour2 );
         
tolerance tolerance * ( 255 255 ) << 0;
         var 
distance:Number 0;
         
distance += Math.powRGB1.red RGB2.red);
        
distance += Math.powRGB1.green RGB2.green);
        
distance += Math.powRGB1.blue RGB2.blue);
         return 
distance <= tolerance;
    }
    public function 
imprimir(){
        var 
_this:Object=this;
        
_this.broadcastMessage("onComplete");
        return 
_this.myBitmap;
        
    }
    public function 
imprimirVector(){
        var 
_this:Object=this;
        
_this.record=new Object();
        
_this.record.width=_this.myBitmap.width;
        
_this.record.height=_this.myBitmap.height;
        
_this.record.cols0;
        
_this.record.rows0;
        var 
progreso:Number;
        var 
intervalo:Number=setInterval(function(){
            
_this.record["px" _this.record.rows] = new Array();
            for(var 
a:Number 0_this.record.widtha++){
                var 
pixel:Number_this.myBitmap.getPixel(a_this.record.rows)
                var 
str_pixel:String pixel.toString(16)
                if(
pixel == 'FFFFFF'str_pixel "";   
                    
_this.record["px" _this.record.rows].push(str_pixel)
            }
               
_this.record.rows += 1;
               
progreso=Math.floor((_this.record.rows/_this.myBitmap.height)*100);
               
_this.broadcastMessage("onProgressPrintVector",progreso);
            if(
_this.record.rows >= _this.myBitmap.height){
                
clearInterval(intervalo);
                
_this.broadcastMessage("onCompletePrintVector",_this.record);
            }
        },
5);
    }
    public function 
del(scope){
        
this.broadcastMessage("onDel");
        
scope['holder_mc'].removeMovieClip();
        
this.myBitmap.dispose();
        
    }
}



A partir de allí, podemos hacer varias cosas:

-Usar el método imprimir para generar un movieClip que contenga nuestro mapa de bits modificado. Esto lo haríamos así:
import ojorojo;//importamos la clase
var ojo=new ojorojo();//creamos una instancia
/*
creamos un listener para los eventos que le asignamos a la clase
y los manejadores correspondientes
*/
lis=new Object();
lis.onStart=function(tm){
    
trace('INTENTANDO CARGAR JPG');    
}
lis.onInit=function(tm){
    
trace('JPG CARGADO');    
}
lis.onError=function(tm){
    
trace('ERROR');    
}
lis.onSelectedColor=function(mc,color){
    
trace('COLOR SELECCIONADO:'+color);
}
lis.onChangeZone=function(centrox,centroy){
    
trace('CAMBIO REALIZADO ALREDEDOR DEL PUNTO [x: '+centrox+', y: '+centroy+']');
}
lis.onComplete=function(){
    
trace('TERMINADO');
}
lis.onDel=function(){
    
trace('Deshaciendo...');
}
ojo.addListener(lis);
ojo.cargarImagen(this,'ojorojo.jpg');//cargamos un jpg
/*creamos la función que use el bitmap modificado para rellenar un movieClip*/
function test(){
    var 
clip=createEmptyMovieClip('pp',getNextHighestDepth());
    var 
bmp=ojo.imprimir().clone();
    
clip._y=bmp.height;
    
clip.attachBitmap(bmp,1);
}
bt.onPress=test;//usamos un botón para llamar a la función



-Usar el método imprimirVector para obtener un array con los colores de cada pixel y las dimensiones de la imagen:
import ojorojo;//importamos la clase
var ojo=new ojorojo();//creamos una instancia
/*
creamos un listener para los eventos que le asignamos a la clase
y los manejadores correspondientes
*/
lis=new Object();
lis.onStart=function(tm){
    
trace('INTENTANDO CARGAR JPG');    
}
lis.onInit=function(tm){
    
trace('JPG CARGADO');    
}
lis.onError=function(tm){
    
trace('ERROR');    
}
lis.onSelectedColor=function(mc,color){
    
trace('COLOR SELECCIONADO:'+color);
}
lis.onChangeZone=function(centrox,centroy){
    
trace('CAMBIO REALIZADO ALREDEDOR DEL PUNTO [x: '+centrox+', y: '+centroy+']');
}
lis.onComplete=function(){
    
trace('TERMINADO');
}
lis.onDel=function(){
    
trace('Deshaciendo...');
}
ojo.addListener(lis);
ojo.cargarImagen(this,'ojorojo.jpg');//cargamos un jpg
/*
creamos una función nos entregue un array con los colores de cada pixel,
más las dimensiones de la imagen
*/
function test2(){
    
ojo.imprimirVector();
    
lis.onProgressPrintVector=function(p){
        
trace(p)
    }
    
lis.onCompletePrintVector=function(v){
        
trace(v['px0'][10]);
    }
}
bt.onPress=test2;//usamos un botón para llamar a la función


En ese código, sólo obtenemos el color de un pixel, pero podríamos modificar esa función para enviar por el método post el vector completo, usando loadVars, a una página php que procese esa información con GD y nos entregue una imagen, usando el código que nos proponen en el segundo enlace que mencionamos al principio de esta nota (Export JPEG with Flash/PHP).
El código php al que nos referimos es el siguiente:
<?php
/*---- credits: http://www.sephiroth.it/tutorials/flashPHP/print_screen/ ----*/
error_reporting(0);
/**
 * Get the width and height of the destination image
 * from the POST variables and convert them into
 * integer values
 */
$w = (int)$_POST['width'];
$h = (int)$_POST['height'];

// create the image with desired width and height

$img imagecreatetruecolor($w$h);

// now fill the image with blank color
// do you remember i wont pass the 0xFFFFFF pixels 
// from flash?
imagefill($img000xFFFFFF);

$rows 0;
$cols 0;

// now process every POST variable which
// contains a pixel color
for($rows 0$rows $h$rows++){
    
// convert the string into an array of n elements
    
$c_row explode(","$_POST['px' $rows]);
    for(
$cols 0$cols $w$cols++){
        
// get the single pixel color value
        
$value $c_row[$cols];
        
// if value is not empty (empty values are the blank pixels)
        
if($value != ""){
            
// get the hexadecimal string (must be 6 chars length)
            // so add the missing chars if needed
            
$hex $value;
            while(
strlen($hex) < 6){
                
$hex "0" $hex;
            }
            
// convert value from HEX to RGB
            
$r hexdec(substr($hex02));
            
$g hexdec(substr($hex22));
            
$b hexdec(substr($hex42));
            
// allocate the new color
            // N.B. teorically if a color was already allocated 
            // we dont need to allocate another time
            // but this is only an example
            
$test imagecolorallocate($img$r$g$b);
            
// and paste that color into the image
            // at the correct position
            
imagesetpixel($img$cols$rows$test);
        }
    }
}

// print out the correct header to the browser
header("Content-type:image/jpeg");
// display the image
imagejpeg($img""90);
?>



-O simplemente podríamos usar el método del para deshacer los cambios y/o recargar otra imagen:
import ojorojo;//importamos la clase
var ojo=new ojorojo();//creamos una instancia
/*
creamos un listener para los eventos que le asignamos a la clase
y los manejadores correspondientes
*/
lis=new Object();
lis.onStart=function(tm){
    
trace('INTENTANDO CARGAR JPG');    
}
lis.onInit=function(tm){
    
trace('JPG CARGADO');    
}
lis.onError=function(tm){
    
trace('ERROR');    
}
lis.onSelectedColor=function(mc,color){
    
trace('COLOR SELECCIONADO:'+color);
}
lis.onChangeZone=function(centrox,centroy){
    
trace('CAMBIO REALIZADO ALREDEDOR DEL PUNTO [x: '+centrox+', y: '+centroy+']');
}
lis.onComplete=function(){
    
trace('TERMINADO');
}
lis.onDel=function(){
    
trace('Deshaciendo...');
}
ojo.addListener(lis);
ojo.cargarImagen(this,'ojorojo.jpg');//cargamos un jpg
/*
creamos una función para resetear y/o recargar otra imagen
*/
var _this=this
function test3(){
    
ojo.del(_this);
    
ojo=null;
    
ojo=new ojorojo();
    
ojo.cargarImagen(_this,'ojorojo.jpg');
    
ojo.addListener(lis);
}
bt.onPress=test3;//usamos un botón para llamar a la función



Creo que los ejemplos mostrados nos ayudan a vislumbrar una vez más la potencia de la clase BitmapData, de la cual seguramente volveremos a hablar en alguna otra oportunidad.

Podés descargar los archivos utilizados usando el botón de descargas ubicado en el lateral izquierdo de esta misma página.
Home - Quiénes Somos - Portfolio - Espacio Diseño - Espacio Programación - Capacitación - Contacto - RSS - XHTML 1.0