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 similar( colour1:uint, colour2:uint, tolerance:Number = 0.01 ):Boolean
{
var RGB1:Object = Hex24ToRGB( colour1 );
var RGB2:Object = Hex24ToRGB( colour2 );
tolerance = tolerance * ( 255 * 255 * 3 ) << 0;
var distance:Number = 0;
distance += Math.pow( RGB1.red - RGB2.red, 2 );
distance += Math.pow( RGB1.green - RGB2.green, 2 );
distance += Math.pow( RGB1.blue - RGB2.blue, 2 );
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._width, this._height, false)
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 r = Number("0x" + hexColor.substr(0,2))
var g = Number("0x" + hexColor.substr(2,2))
var b = Number("0x" + hexColor.substr(4,2))
testo.text = "0x" + hexColor + ", {r:" + r + ", g:" + g + ", b:" + 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._width, holder_mc._height,true,0x00FFFFFF);
m = new Matrix();
m.scale(1,1);
m.translate(0,0);
myBitmap.draw(holder_mc,m);
attachBitmap(myBitmap,1);
var rect:Rectangle = new Rectangle(0, 0, myBitmap.width, myBitmap.height);
var bmpTem=new BitmapData(myBitmap.width, myBitmap.height,true,0x00FFFFFF);
bmpTem.copyPixels(myBitmap,new Rectangle(0, 0, myBitmap.width, myBitmap.height),new Point(0,0));
bmpTem.copyChannel(bmpTem, rect, new Point(0, 0), 2, 1);
bmpTem.copyChannel(bmpTem, rect, new Point(0, 0), 2, 2);
bmpTem.copyChannel(bmpTem, rect, new Point(0, 0), 2, 4);
myBitmap.copyPixels(bmpTem,new Rectangle(0, 0, myBitmap.width, myBitmap.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._width, holder_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:Number, r:Number, g:Number, b:Number){
return (a<<24 | r<<16 | g<<8 | 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.similar( pixelColor, color, 0.05 ) && (Point.distance(centro,aevaluar)<=10)){
var rect:Rectangle = new Rectangle(0, 0, 1, 1);
var bmpTem:BitmapData=new BitmapData(1, 1,true,0x00FFFFFF);
bmpTem.copyPixels(_this.myBitmap,new Rectangle(i, j, 1, 1),new Point(0,0));
bmpTem.copyChannel(bmpTem, rect, new Point(0, 0), 2, 1);
bmpTem.copyChannel(bmpTem, rect, new Point(0, 0), 2, 2);
bmpTem.copyChannel(bmpTem, rect, new Point(0, 0), 2, 4);
_this.myBitmap.copyPixels(bmpTem,new Rectangle(0, 0, 1, 1),new Point(i,j));
bmpTem.dispose();
}
}
}
_this.broadcastMessage("onChangeZone", Px,Py);
}
private function similar( colour1, colour2, tolerance ){
var _this:Object=this;
var RGB1:Object = _this.hextoargb( colour1 );
var RGB2:Object = _this.hextoargb( colour2 );
tolerance = tolerance * ( 255 * 255 * 3 ) << 0;
var distance:Number = 0;
distance += Math.pow( RGB1.red - RGB2.red, 2 );
distance += Math.pow( RGB1.green - RGB2.green, 2 );
distance += Math.pow( RGB1.blue - RGB2.blue, 2 );
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.cols= 0;
_this.record.rows= 0;
var progreso:Number;
var intervalo:Number=setInterval(function(){
_this.record["px" + _this.record.rows] = new Array();
for(var a:Number = 0; a < _this.record.width; a++){
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($img, 0, 0, 0xFFFFFF);
$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($hex, 0, 2));
$g = hexdec(substr($hex, 2, 2));
$b = hexdec(substr($hex, 4, 2));
// 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.