Exemplo de bitmap: lua giratória animadaFlash Player 9 e posterior, Adobe AIR 1.0 e posterior 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:
Para obter os arquivos do aplicativo para este 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:
Carregamento de uma imagem externa como dados de bitmapA 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 subsequentes. 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. Consequentemente, 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 é usada como uma origem de dados de imagem para criar a animação da lua giratória. Isso é descrito a seguir. Criação da animação pela cópia de pixelsUma 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. 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 da lua em retângulo. Além disso, para criar a rotação animada, o código 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:
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.Data.copyPixels(textureMap, new Rectangle(sourceX, 0, sphere.width, sphere.height), new Point(0, 0)); event.updateAfterEvent(); } O código faz três coisas:
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éricaA 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 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 pixelUm 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:
|
![]() |