Exemplo: Lua giratória animada



O exemplo de lua giratória animada demonstra técnicas para trabalhar com objetos Bitmap e dados de imagem de bitmap (objetos BitmapData). O exemplo cria uma animação de uma lua giratória e esférica utilizando uma imagem plana da superfície da lua como os dados de imagem não processados. As técnicas a seguir são demonstradas:

  • Carregamento de uma imagem externa e acesso aos seus dados de imagem não processados

  • Criação de animação por meio de cópia repetida de pixels de diferentes partes de uma imagem de origem

  • Criação de uma imagem de bitmap pela definição de valores de pixel

Para obter os arquivos de aplicativo desse exemplo, consulte www.adobe.com/go/learn_programmingAS3samples_flash_br. Os arquivos de aplicativo da lua giratório animada podem ser encontrados na pasta Samples/SpinningMoon. O aplicativo consiste nos seguintes arquivos:

Arquivo

Descrição

SpinningMoon.mxml

ou

SpinningMoon.fla

O arquivo principal do aplicativo no Flex (MXML) ou Flash (FLA).

com/example/programmingas3/moon/MoonSphere.as

A classe que executa a funcionalidade de carregamento, exibição e animação da lua.

moonMap.png

O arquivo de imagem que contém uma fotografia da superfície da lua, que é carregado e utilizado para criar a lua giratório e animada.

Carregamento de uma imagem externa como dados de bitmap

A primeira tarefa principal que essa amostra executa é o carregamento de um arquivo de imagem externa, que é uma fotografia da superfície da lua. A operação de carregamento é manipulada por dois métodos na classe MoonSphere: o construtor MoonSphere(), em que o processo de carregamento é iniciado, e o método imageLoadComplete(), que é chamado quando a imagem externa está totalmente carregada.

O carregamento de uma imagem externa é semelhante ao carregamento de um SWF externo; ambos utilizam uma ocorrência da classe flash.display.Loader para executar essa operação. O código real no método MoonSphere() que inicia o carregamento de uma imagem é o seguinte:

var imageLoader:Loader = new Loader(); 
imageLoader.contentLoaderInfo.addEventListener(Event.COMPLETE, imageLoadComplete); 
imageLoader.load(new URLRequest("moonMap.png"));

A primeira linha declara que a ocorrência de Loader é chamada imageLoader. A terceira linha inicia realmente o processo de carregamento chamando o método load() do objeto Loader, transmitindo uma ocorrência de URLRequest que representa a URL da imagem a ser carregada. A segunda linha configura o ouvinte do evento que será acionado quando a imagem for totalmente carregada. Observe que o método addEventListener() não é chamado na própria ocorrência de Loader; em vez disso, ele é chamado na propriedade contentLoaderInfo do objeto Loader. A própria ocorrência de Loader não despacha eventos relacionados ao conteúdo sendo carregado. A propriedade contentLoaderInfo, entretanto, contém uma referência ao objeto LoaderInfo que é associado ao conteúdo sendo carregado no objeto Loader (a imagem externa nesse caso). Aquele objeto LoaderInfo fornece vários eventos relacionados ao progresso e à conclusão do carregamento de conteúdo externo, incluindo o evento complete (Event.COMPLETE) que acionará uma chamada para o método imageLoadComplete() quando a imagem for totalmente carregada.

Embora o início do carregamento da imagem externa seja uma parte importante do processo, é igualmente importante saber o que fazer quando o carregamento for concluído. Como mostrado no código acima, a função imageLoadComplete() é chamada quando a imagem é carregada. Aquela função faz várias atividades com os dados de imagem carregados, descritas nas seções subseqüentes. Contudo, para usar os dados da imagem, é necessário acessar aqueles dados. Quando o objeto Loader for utilizado para carregar uma imagem externa, a imagem carregada torna-se uma ocorrência de bitmap que é anexada a um objeto de exibição filho do objeto Loader. Nesse caso, a ocorrência de Loader está disponível para o método do ouvinte do evento como parte do objeto de evento que é transmitido para o método como um parâmetro. As primeiras linhas do método imageLoadComplete() são como se segue:

private function imageLoadComplete(event:Event):void 
{ 
    textureMap = event.target.content.bitmapData; 
    ... 
}

Observe que o parâmetro do objeto de evento é chamado event, e é uma ocorrência da classe Event. Cada ocorrência da classe Event tem uma propriedade target, que se refere ao objeto que está acionando o evento (nesse caso, a ocorrência de LoaderInfo na qual o método addEventListener() foi chamado, conforme descrito anteriormente). O objeto LoaderInfo, por sua vez, tem uma propriedade content que (uma vez concluído o processo de carregamento) contém a ocorrência de Bitmap com a imagem de bitmap carregada. Se você quiser exibir a imagem diretamente na tela, poderá anexar essa ocorrência de Bitmap (event.target.content) a um contêiner do objeto de exibição. (Você também pode anexar o objeto Loader a um contêiner do objeto de exibição). Entretanto, nessa amostra, o conteúdo carregado é utilizado como uma origem dos dados da imagem não processados que estão sendo exibidos na tela. Conseqüentemente, a primeira linha do método imageLoadComplete() lê a propriedade bitmapData da ocorrência de Bitmap carregada (event.target.content.bitmapData) e a armazena na variável de ocorrência chamada textureMap, que, como descrito na seção a seguir, é usada como uma origem de dados de imagem para criar a animação da lua giratória.

Criação da animação pela cópia de pixels

Uma definição básica da animação é a ilusão de movimentação, ou alteração, criada pela alteração de uma imagem ao longo do tempo. Nessa amostra, o objetivo é criar a ilusão de uma lua esférica girando ao redor de seu eixo vertical. Contudo, para o propósito de animação, você pode ignorar o aspecto de distorção esférica da amostra. Considere a imagem real que está carregada e é usada como a origem dos dados da imagem de lua:

Como você pode ver, a imagem não uma nem várias esferas; ela é uma fotografia retangular da superfície da lua. Como a foto foi tirada exatamente na linha do equador da lua, as partes da imagem próximas à parte superior e inferior da imagem são expandidas e distorcidas. Para remover a distorção da imagem e deixá-la com uma aparência esférica, utilizaremos um filtro de mapa de deslocamento, como descrito posteriormente. Entretanto, como essa imagem de origem é um retângulo, para criar a ilusão de que a esfera está girando, o código simplesmente precisa deslizar a foto da superfície da lua horizontalmente, como descrito nos parágrafos a seguir.

Observe que a imagem realmente contém duas cópias da fotografia da superfície da lua próximas uma da outra. Essa imagem é a imagem de origem da qual os dados de imagem foram copiados repetidas vezes para criar a aparência de movimento. Tendo duas cópias da imagem próximas uma da outra, um efeito de rolagem contínuo e ininterrupto pode ser criado com mais facilidade. Vamos avançar no processo de animação etapa por etapa para ver como ele funciona.

O processo realmente envolve dois objetos separados do ActionScript. Primeiro, existe a imagem de origem carregada, que no código é representada pela ocorrência de BitmapData chamada textureMap. Como descrito anteriormente, textureMap é preenchido com os dados de imagem assim que a imagem externa é carregada, utilizando este código:

textureMap = event.target.content.bitmapData;

O conteúdo de textureMap é a imagem mostrada anteriormente. Além disso, para criar a rotação animada, a amostra usa uma ocorrência de Bitmap chamada sphere, que é o objeto de exibição real que mostra a imagem da lua na tela. Como textureMap, o objeto sphere é criado e preenchido com seus dados de imagem iniciais no método imageLoadComplete(), utilizando o seguinte código:

sphere = new Bitmap(); 
sphere.bitmapData = new BitmapData(textureMap.width / 2, textureMap.height); 
sphere.bitmapData.copyPixels(textureMap, 
                         new Rectangle(0, 0, sphere.width, sphere.height), 
                         new Point(0, 0));

Como mostra o código, sphere é instanciado. Sua propriedade bitmapData (os dados de imagem não processados que são exibidos por sphere) é criada com a mesma altura e metade da largura de textureMap. Em outras palavras, o conteúdo de sphere será o tamanho de uma foto da lua (desde que a imagem textureMap contenha duas fotos da lua lado a lado). Em seguida, a propriedade bitmapData é preenchida com dados de imagem utilizando o método copyPixels(). Os parâmetros na chamada do método copyPixels() indicam várias coisas:

  • O primeiro parâmetro indica que os dados da imagem são copiados de textureMap.

  • O segundo parâmetro, uma nova ocorrência de Rectangle, especifica de qual parte de textureMap o snapshot da imagem deve ser tirado; nesse caso, o snapshot é um retângulo que começa no canto superior esquerdo de textureMap (indicado pelos dois primeiros parâmetros Rectangle(): 0, 0) e a largura e a altura do snapshot do retângulo corresponde às propriedades width e height desphere.

  • O terceiro parâmetro, uma nova ocorrência de Point com os valores x e y de 0, define o destino dos dados de pixel — nesse caso, o canto superior esquerdo (0, 0) de sphere.bitmapData.

Representado visualmente, o código copia os pixels de textureMap contornados na imagem a seguir e os cola em sphere. Em outras palavras, o conteúdo de BitmapData desphere é a parte de textureMap em destaque aqui:

Lembre-se, contudo, de que esse é apenas o estado inicial de sphere — o conteúdo da primeira imagem que é copiada na sphere.

Com a imagem de origem carregada e sphere criada, a tarefa final executada pelo método imageLoadComplete() é a configuração da animação. A animação é orientada pela ocorrência de Timer chamada rotationTimer, que é criada e iniciada pelo seguinte código:

var rotationTimer:Timer = new Timer(15); 
rotationTimer.addEventListener(TimerEvent.TIMER, rotateMoon); 
rotationTimer.start();

O código cria primeiro a ocorrência de Timer chamada rotationTimer; o parâmetro transmitido para o construtor Timer() indica que rotationTimer deve acionar seu evento timer a cada 15 milissegundos. Em seguida, o método addEventListener() é chamado, especificando que, quando o evento timer (TimerEvent.TIMER) ocorre, o método rotateMoon() é chamado. Por fim, o temporizado é realmente iniciado chamando seu método start().

Devido ao modo com rotationTimer está definido, aproximadamente a cada 15 milissegundos, o Flash Player chama o método rotateMoon() na classe MoonSphere, que é onde a animação da lua acontece. O código-fonte do método rotateMoon() é o seguinte:

private function rotateMoon(event:TimerEvent):void 
{ 
    sourceX += 1; 
    if (sourceX > textureMap.width / 2) 
    { 
        sourceX = 0; 
    } 
     
    sphere.bitmapData.copyPixels(textureMap, 
                                    new Rectangle(sourceX, 0, sphere.width, sphere.height), 
                                    new Point(0, 0)); 
     
    event.updateAfterEvent(); 
}

O código faz três coisas:

  1. O valor da variável sourceX (inicialmente definido como 0) é incrementado em 1.

    sourceX += 1;

    Como você verá, sourceX é usado para determinar o local em textureMap do qual os pixels serão copiados para sphere; portanto esse código tem o efeito de mover o retângulo um pixel para a direita em textureMap. Voltando à representação visual, depois de vários ciclos de animação, o retângulo de origem terá se movido vários pixels para a direita, como se segue:

    Depois de vários outros ciclos, o retângulo terá se movido para mais longe:

    Esse deslocamento gradual, uniforme no local do qual os pixels são copiados é a chave da animação. Movendo lenta e continuamente o local de origem para a direita, a imagem que é exibida na tela em sphere parece deslizar continuamente para a esquerda. Essa é a razão pela qual a imagem de origem (textureMap) precisa ter duas cópias da foto da superfície da lua. Como o retângulo se move continuamente para a direita, a maioria das vezes ele não está sobre uma única foto da lua, mas sobrepõe as duas fotos da lua.

  2. Com o retângulo de origem movendo-se lentamente para a direita, há um problema. Finalmente, o retângulo atingirá a borda direita de textureMap e ficará sem os pixels da foto da lua para copiar na sphere:

    As linhas de código a seguir solucionam esse problema:

    if (sourceX >= textureMap.width / 2) 
    { 
        sourceX = 0; 
    }

    O código verifica se sourceX (a borda esquerda do retângulo) atingiu o meio de textureMap. Em caso afirmativo, ele redefine sourceX para 0, movendo-o para a borda esquerda de textureMap e iniciando o ciclo novamente:

  3. Com o valor sourceX apropriado calculado, a etapa final na criação da animação é copiar realmente os pixels do novo retângulo de origem para sphere. O código que faz isso é muito semelhante ao código que inicialmente preenche sphere (descrito anteriormente); a única diferença é que nesse caso, na chamada do construtor new Rectangle(), a borda esquerda do retângulo é colocada em sourceX:

    sphere.bitmapData.copyPixels(textureMap, 
                                new Rectangle(sourceX, 0, sphere.width, sphere.height), 
                                new Point(0, 0));

Lembre-se de que esse código é chamada repetidamente a cada 15 milissegundos. Como o local do retângulo de origem é deslocado de forma contínua, e os pixels são copiados em sphere, a aparência na tela é a de que a imagem da foto da lua representada por sphere desliza continuamente. Em outras palavras, a lua parece girar continuamente.

Criação da aparência esférica

A lua, é claro, é uma esfera e não um retângulo. Conseqüentemente, a mostra precisa pegar a foto da superfície da lua retangular, à medida que é animada continuamente, e convertê-la em uma esfera. Isso envolve duas etapas separadas: uma máscara é usada para ocultar todo o conteúdo exceto uma região circular da foto da superfície da lua, e um filtro de mapa de deslocamento é usado para distorcer a aparência da foto da lua para que ela pareça tridimensional.

Primeiro, uma máscara no formato de círculo é usada para ocultar todo o conteúdo do objeto MoonSphere exceto a esfera criada pelo filtro. O código a seguir cria a máscara como uma ocorrência de Shape e a aplica como a máscara da ocorrência MoonSphere:

moonMask = new Shape(); 
moonMask.graphics.beginFill(0); 
moonMask.graphics.drawCircle(0, 0, radius); 
this.addChild(moonMask); 
this.mask = moonMask;

Observe que, como MoonSphere é um objeto de exibição (ele é baseado na classe Sprite), a máscara pode ser aplicada diretamente à ocorrência de MoonSphere usando sua propriedade mask herdada.


Ocultar simplesmente partes da foto utilizando uma máscara em forma de círculo não é suficiente para criar um efeito de esfera giratória realista. Devido à forma como a foto da superfície da lua foi tirada, suas dimensões não são proporcionais; as partes da imagem que estão mais próximas à parte superior ou inferior da imagem são mais distorcidas e expandidas em comparação com as partes na linha do equador. Para distorcer a aparência da foto da lua e torná-la tridimensional, usaremos um filtro de mapa de deslocamento.

Um filtro de mapa de deslocamento é um tipo de filtro utilizado para distorcer uma imagem. Nesse caso, a foto da lua será "distorcida" para torná-la mais realista, comprimindo a parte superior e inferior da imagem horizontalmente, enquanto deixa o meio inalterado. Considerando que o filtro funciona em uma parte em formato de quadrado da foto, comprimir a parte superior e a inferior, mas não o meio, transformará o quadrado em um círculo. Um efeito colateral da animação dessa imagem distorcida é que o meio da imagem parece mover-se para mais longe em distância real de pixels do que as áreas próximas à parte superior e inferior, o que cria a ilusão de que o círculo é realmente um objeto tridimensional (uma esfera).

O código a seguir é usado para criar o filtro do mapa de deslocamento chamado displaceFilter:

var displaceFilter:DisplacementMapFilter; 
displaceFilter = new DisplacementMapFilter(fisheyeLens, 
                                new Point(radius, 0),  
                                BitmapDataChannel.RED, 
                                BitmapDataChannel.GREEN, 
                                radius, 0);

Esse primeiro parâmetro, fisheyeLens, é conhecido como a imagem de mapa; nesse caso, um objeto BitmapData que é criado de modo programático. A criação daquela imagem é descrita abaixo na seção Criação de uma imagem de bitmap pela definição de valores de pixel. Os outros parâmetros descrevem a posição na imagem filtrada na qual o filtro deve ser aplicado, os canais de cor que serão utilizados para controlar o efeito do deslocamento e até qual extensão eles afetarão o deslocamento. Depois que o filtro de mapa de deslocamento é criado, ele é aplicado a sphere, ainda dentro do método imageLoadComplete():

sphere.filters = [displaceFilter];

A imagem final, com a máscara e o filtro de mapa de deslocamento aplicados, se parece com:


Com cada ciclo de animação da lua giratória, o conteúdo de BitmapData da esfera é sobregravado por um novo snapshot dos dados da imagem de origem. Contudo, o filtro não precisa ser reaplicado sempre. Isso porque o filtro é aplicado à ocorrência de Bitmap (o objeto de exibição) em vez de aos dados do bitmap (as informações em pixels não processadas). Lembre-se, a ocorrência do Bitmap não são os dados reais de bitmap; ela é uma objeto de exibição que mostra os dados de bitmap na tela. Para usar uma analogia, uma ocorrência de Bitmap é como o projeto de slides que é usado para exibir slides fotográficos em uma tela; um objeto BitmapData é como o slide de fotografia real que pode ser apresentando por de um projetor de slide. Um filtro pode ser aplicado diretamente a um objeto BitmapData, que seria comparável a desenhar diretamente no slide fotográfico para alterar a imagem. Um filtro também pode ser aplicado a qualquer objeto de exibição, inclusive uma ocorrência de Bitmap; isso seria como colocar um filtro na frente das lentes do projetor de slides para distorcer a saída mostrada na tela (sem alterar o slide original). Como os dados de bitmap não processados estão acessíveis por meio da propriedade bitmapData da ocorrência de Bitmap, o filtro pode ter sido aplicado diretamente nos dados de bitmap não processados. Entretanto, nesse caso, faz sentido aplicar o filtro ao objeto de exibição Bitmap em vez dos dados de bitmap.

Para obter informações detalhadas sobre o uso de filtro de mapas de deslocamento no ActionScript, consulte Filtro de objetos de exibição.

Criação de uma imagem de bitmap pela definição de valores de pixel

Um importante aspecto de um filtro de mapa de deslocamento é que ele realmente envolve duas imagens. Uma imagem, a imagem de origem, é a imagem que será realmente alterada pelo filtro. Nessa amostra, a imagem de origem é a ocorrência de Bitmap chamada sphere. A outra imagem usada pelo filtro é conhecida como a imagem de mapa. A imagem do mapa não é realmente exibida na ela. Em vez disso, a cor de cada um de seus pixels é usada como uma entrada para a função de deslocamento — a cor do pixel em uma determinada coordenada x, y na imagem de mapa determina quanto de deslocamento (deslocamento físico na posição) é aplicado ao pixel naquelas coordenadas x, y na imagem de origem.

Conseqüentemente, para usar o filtro de mapa de deslocamento para criar um efeito de esfera, a amostra precisa de imagem de mapa apropriada — aquela que tem um plano de fundo cinza e um círculo que é preenchido com um gradiente de uma única cor (vermelho) indo horizontalmente de escuro para claro, como mostrado aqui:


Como somente uma imagem e um filtro de mapa são utilizados nesta amostra, a imagem do mapa é criada apenas uma vez, no método imageLoadComplete() (em outras palavras, quando a imagem externa acabar de ser carregada. A imagem do mapa, chamada fisheyeLens, é criada chamando o método createFisheyeMap() da classe MoonSphere:

var fisheyeLens:BitmapData = createFisheyeMap(radius);

Dentro do método createFisheyeMap(), a imagem de mapa é desenhada um pixel por vez utilizando o método setPixel() da classe BitmapData. O código completo para o método createFisheyeMap() é listado aqui, seguido por uma discussão etapa por etapa de como ele funciona:

private function createFisheyeMap(radius:int):BitmapData 
{ 
    var diameter:int = 2 * radius; 
     
    var result:BitmapData = new BitmapData(diameter, 
                                        diameter, 
                                        false, 
                                        0x808080); 
     
    // Loop through the pixels in the image one by one 
    for (var i:int = 0; i < diameter; i++) 
    { 
        for (var j:int = 0; j < diameter; j++) 
        { 
            // Calculate the x and y distances of this pixel from 
            // the center of the circle (as a percentage of the radius). 
            var pctX:Number = (i - radius) / radius; 
            var pctY:Number = (j - radius) / radius; 
             
            // Calculate the linear distance of this pixel from 
            // the center of the circle (as a percentage of the radius). 
            var pctDistance:Number = Math.sqrt(pctX * pctX + pctY * pctY); 
             
            // If the current pixel is inside the circle, 
            // set its color. 
            if (pctDistance < 1) 
            { 
                // Calculate the appropriate color depending on the 
                // distance of this pixel from the center of the circle. 
                var red:int; 
                var green:int; 
                var blue:int; 
                var rgb:uint; 
                red = 128 * (1 + 0.75 * pctX * pctX * pctX / (1 - pctY * pctY)); 
                green = 0; 
                blue = 0; 
                rgb = (red << 16 | green << 8 | blue); 
                // Set the pixel to the calculated color. 
                result.setPixel(i, j, rgb); 
            } 
        } 
    } 
    return result; 
}

Primeiro, quando o método é chamado, ele recebe um parâmetro, radius, indicando o raio da imagem em forma de círculo a ser criado. Em seguida, o código cria o objeto BitmapData no qual o círculo será desenhado. Aquele objeto, chamado result, é por fim transmitido de volta como o valor de retorno do método. Como mostrado no snippet de código a seguir, a ocorrência de BitmapData de result é criada com a largura e a altura tão grandes quando o diâmetro do círculo, sem transparência (false para o terceiro parâmetro), e preenchida com a cor 0x808080 (cinza médio):

var result:BitmapData = new BitmapData(diameter, 
                                    diameter, 
                                    false, 
                                    0x808080);

Em seguida, o código usa loops para iterar cada pixel da imagem. O loop externo engloba cada coluna da imagem da esquerda para a direita (utilizando a variável i para representar a posição horizontal do pixel atualmente sendo manipulado), enquanto o loop interno engloba cada pixel da coluna atual de cima para baixo (com a variável j representando a posição vertical de pixels atual). O código para os loops (com o conteúdo do loop interno omitido) é mostrado aqui:

for (var i:int = 0; i < diameter; i++) 
{ 
    for (var j:int = 0; j < diameter; j++) 
    { 
        ... 
    } 
}

À medida que o loop engloba os pixels um a um, em cada pixel um valor (o valor de cor daquele pixel na imagem de mapa) é calculado. Esse processo envolve quatro etapas:

  1. O código calcula a distância do pixel atual do centro do círculo ao longo do eixo x (i - radius). Esse valor é dividido pelo raio para torná-lo um percentual de raio em vez de uma distância absoluta ((i - radius) / radius). Esse valor de percentual é armazenado em uma variável chamada pctX, e o valor equivalente para o eixo y é calculado e armazenado na variável pctY, como mostra este código:

    var pctX:Number = (i - radius) / radius; 
    var pctY:Number = (j - radius) / radius;
  2. Usando uma fórmula trigonométrica padrão, o Teorema de Pitágoras, a distância linear entre o cento do círculo e o ponto atual é calculado de pctX e pctY. Esse valor é armazenado em uma variável chamada pctDistance, como mostrado aqui:

    var pctDistance:Number = Math.sqrt(pctX * pctX + pctY * pctY);
  3. Em seguida, o código verifica se o percentual de distância é menor do que 1 (significando 100% do raio, ou em outras palavras, se o pixel sendo considerado está dentro do raio do círculo). Se o pixel estiver dentro do círculo, ele recebe um valor de cor calculado (omitido aqui, mas descrito na etapa 4); em caso negativo, nada mais acontece com aquele pixel; sua cor é deixada como o padrão de cinza médio:

    if (pctDistance < 1) 
    { 
        ... 
    }
  4. Para aqueles pixels que estiverem dentro do círculo, um valor de cor é calculado para o pixel. A cor final será uma sombra de vermelho variando de preto (0% de vermelho) na borda esquerda do círculo até vermelho vivo (100%) na borda direita do círculo. O valor da cor é inicialmente calculado em três partes (vermelho, verde e azul), como mostrado aqui:BitmapData

    red = 128 * (1 + 0.75 * pctX * pctX * pctX / (1 - pctY * pctY)); 
    green = 0; 
    blue = 0;

    Observe que apenas a parte vermelha da cor (a variável red) tem realmente um valor. Os valores de verde e azul (as variáveis green e blue) são mostrados aqui para fins de explicação, mas podem ser omitidos. Como o propósito desse método é criar um círculo que contenha um gradiente vermelho, nenhum valor de verde ou azul é necessário.

    Depois que os três valores de cores individuais estiverem determinados, eles serão combinados em um único valor de cor inteiro utilizando um algoritmo de deslocamento de bits padrão, mostrado neste código:

    rgb = (red << 16 | green << 8 | blue);

    Finalmente, com o valor de cor calculado, aquele valor é atribuído ao pixel atual utilizando o método setPixel() do objeto result BitmapData, mostrado aqui:

    result.setPixel(i, j, rgb);