mirror of https://github.com/aseprite/aseprite.git
				
				
				
			
		
			
				
	
	
		
			589 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
			
		
		
	
	
			589 lines
		
	
	
		
			13 KiB
		
	
	
	
		
			C++
		
	
	
	
| // Aseprite Document Library
 | |
| // Copyright (c) 2001-2015 David Capello
 | |
| //
 | |
| // This file is released under the terms of the MIT license.
 | |
| // Read LICENSE.txt for more information.
 | |
| 
 | |
| #ifdef HAVE_CONFIG_H
 | |
| #include "config.h"
 | |
| #endif
 | |
| 
 | |
| #include "doc/sprite.h"
 | |
| 
 | |
| #include "base/memory.h"
 | |
| #include "base/remove_from_container.h"
 | |
| #include "base/unique_ptr.h"
 | |
| #include "doc/cels_range.h"
 | |
| #include "doc/doc.h"
 | |
| #include "doc/frame_tag.h"
 | |
| #include "doc/image_bits.h"
 | |
| #include "doc/primitives.h"
 | |
| 
 | |
| #include <cstring>
 | |
| #include <vector>
 | |
| 
 | |
| namespace doc {
 | |
| 
 | |
| static Layer* index2layer(const Layer* layer, const LayerIndex& index, int* index_count);
 | |
| static LayerIndex layer2index(const Layer* layer, const Layer* find_layer, int* index_count);
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Constructors/Destructor
 | |
| 
 | |
| Sprite::Sprite(PixelFormat format, int width, int height, int ncolors)
 | |
|   : Object(ObjectType::Sprite)
 | |
|   , m_document(NULL)
 | |
|   , m_format(format)
 | |
|   , m_width(width)
 | |
|   , m_height(height)
 | |
|   , m_frames(1)
 | |
|   , m_frameTags(this)
 | |
| {
 | |
|   ASSERT(width > 0 && height > 0);
 | |
| 
 | |
|   m_frlens.push_back(100);      // First frame with 100 msecs of duration
 | |
|   m_folder = new LayerFolder(this);
 | |
| 
 | |
|   // Generate palette
 | |
|   Palette pal(frame_t(0), ncolors);
 | |
| 
 | |
|   switch (format) {
 | |
| 
 | |
|     // For colored images
 | |
|     case IMAGE_RGB:
 | |
|     case IMAGE_INDEXED:
 | |
|       pal.resize(ncolors);
 | |
|       break;
 | |
| 
 | |
|     // For black and white images
 | |
|     case IMAGE_GRAYSCALE:
 | |
|     case IMAGE_BITMAP:
 | |
|       for (int c=0; c<ncolors; c++) {
 | |
|         int g = 255 * c / (ncolors-1);
 | |
|         g = MID(0, g, 255);
 | |
|         pal.setEntry(c, rgba(g, g, g, 255));
 | |
|       }
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   // Initial RGB map
 | |
|   m_rgbMap = NULL;
 | |
| 
 | |
|   // The transparent color for indexed images is 0 by default
 | |
|   m_transparentColor = 0;
 | |
| 
 | |
|   setPalette(&pal, true);
 | |
| }
 | |
| 
 | |
| Sprite::~Sprite()
 | |
| {
 | |
|   // Destroy layers
 | |
|   delete m_folder;
 | |
| 
 | |
|   // Destroy palettes
 | |
|   {
 | |
|     PalettesList::iterator end = m_palettes.end();
 | |
|     PalettesList::iterator it = m_palettes.begin();
 | |
|     for (; it != end; ++it)
 | |
|       delete *it;               // palette
 | |
|   }
 | |
| 
 | |
|   // Destroy RGB map
 | |
|   delete m_rgbMap;
 | |
| }
 | |
| 
 | |
| // static
 | |
| Sprite* Sprite::createBasicSprite(doc::PixelFormat format, int width, int height, int ncolors)
 | |
| {
 | |
|   // Create the sprite.
 | |
|   base::UniquePtr<doc::Sprite> sprite(new doc::Sprite(format, width, height, ncolors));
 | |
|   sprite->setTotalFrames(doc::frame_t(1));
 | |
| 
 | |
|   // Create the main image.
 | |
|   doc::ImageRef image(doc::Image::create(format, width, height));
 | |
|   doc::clear_image(image, 0);
 | |
| 
 | |
|   // Create the first transparent layer.
 | |
|   {
 | |
|     base::UniquePtr<doc::LayerImage> layer(new doc::LayerImage(sprite));
 | |
|     layer->setName("Layer 1");
 | |
| 
 | |
|     // Create the cel.
 | |
|     {
 | |
|       base::UniquePtr<doc::Cel> cel(new doc::Cel(doc::frame_t(0), image));
 | |
|       cel->setPosition(0, 0);
 | |
| 
 | |
|       // Add the cel in the layer.
 | |
|       layer->addCel(cel);
 | |
|       cel.release();            // Release the cel because it is in the layer
 | |
|     }
 | |
| 
 | |
|     // Add the layer in the sprite.
 | |
|     sprite->folder()->addLayer(layer.release()); // Release the layer because it's owned by the sprite
 | |
|   }
 | |
| 
 | |
|   return sprite.release();
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Main properties
 | |
| 
 | |
| void Sprite::setPixelFormat(PixelFormat format)
 | |
| {
 | |
|   m_format = format;
 | |
| }
 | |
| 
 | |
| void Sprite::setSize(int width, int height)
 | |
| {
 | |
|   ASSERT(width > 0);
 | |
|   ASSERT(height > 0);
 | |
| 
 | |
|   m_width = width;
 | |
|   m_height = height;
 | |
| }
 | |
| 
 | |
| bool Sprite::needAlpha() const
 | |
| {
 | |
|   switch (m_format) {
 | |
|     case IMAGE_RGB:
 | |
|     case IMAGE_GRAYSCALE:
 | |
|       return (backgroundLayer() == NULL);
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| bool Sprite::supportAlpha() const
 | |
| {
 | |
|   switch (m_format) {
 | |
|     case IMAGE_RGB:
 | |
|     case IMAGE_GRAYSCALE:
 | |
|       return true;
 | |
|   }
 | |
|   return false;
 | |
| }
 | |
| 
 | |
| void Sprite::setTransparentColor(color_t color)
 | |
| {
 | |
|   m_transparentColor = color;
 | |
| 
 | |
|   // Change the mask color of all images.
 | |
|   std::vector<Image*> images;
 | |
|   getImages(images);
 | |
|   for (Image* image : images)
 | |
|     image->setMaskColor(color);
 | |
| }
 | |
| 
 | |
| int Sprite::getMemSize() const
 | |
| {
 | |
|   int size = 0;
 | |
| 
 | |
|   std::vector<Image*> images;
 | |
|   getImages(images);
 | |
|   for (Image* image : images)
 | |
|     size += image->getRowStrideSize() * image->height();
 | |
| 
 | |
|   return size;
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Layers
 | |
| 
 | |
| LayerFolder* Sprite::folder() const
 | |
| {
 | |
|   return m_folder;
 | |
| }
 | |
| 
 | |
| LayerImage* Sprite::backgroundLayer() const
 | |
| {
 | |
|   if (folder()->getLayersCount() > 0) {
 | |
|     Layer* bglayer = *folder()->getLayerBegin();
 | |
| 
 | |
|     if (bglayer->isBackground()) {
 | |
|       ASSERT(bglayer->isImage());
 | |
|       return static_cast<LayerImage*>(bglayer);
 | |
|     }
 | |
|   }
 | |
|   return NULL;
 | |
| }
 | |
| 
 | |
| LayerIndex Sprite::countLayers() const
 | |
| {
 | |
|   return LayerIndex(folder()->getLayersCount());
 | |
| }
 | |
| 
 | |
| LayerIndex Sprite::firstLayer() const
 | |
| {
 | |
|   return LayerIndex(0);
 | |
| }
 | |
| 
 | |
| LayerIndex Sprite::lastLayer() const
 | |
| {
 | |
|   return LayerIndex(folder()->getLayersCount()-1);
 | |
| }
 | |
| 
 | |
| Layer* Sprite::layer(int layerIndex) const
 | |
| {
 | |
|   return indexToLayer(LayerIndex(layerIndex));
 | |
| }
 | |
| 
 | |
| Layer* Sprite::indexToLayer(LayerIndex index) const
 | |
| {
 | |
|   if (index < LayerIndex(0))
 | |
|     return NULL;
 | |
| 
 | |
|   int index_count = -1;
 | |
|   return index2layer(folder(), index, &index_count);
 | |
| }
 | |
| 
 | |
| LayerIndex Sprite::layerToIndex(const Layer* layer) const
 | |
| {
 | |
|   int index_count = -1;
 | |
|   return layer2index(folder(), layer, &index_count);
 | |
| }
 | |
| 
 | |
| void Sprite::getLayersList(std::vector<Layer*>& layers) const
 | |
| {
 | |
|   // TODO support subfolders
 | |
|   LayerConstIterator it = m_folder->getLayerBegin();
 | |
|   LayerConstIterator end = m_folder->getLayerEnd();
 | |
| 
 | |
|   for (; it != end; ++it) {
 | |
|     layers.push_back(*it);
 | |
|   }
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Palettes
 | |
| 
 | |
| Palette* Sprite::palette(frame_t frame) const
 | |
| {
 | |
|   ASSERT(frame >= 0);
 | |
| 
 | |
|   Palette* found = NULL;
 | |
| 
 | |
|   PalettesList::const_iterator end = m_palettes.end();
 | |
|   PalettesList::const_iterator it = m_palettes.begin();
 | |
|   for (; it != end; ++it) {
 | |
|     Palette* pal = *it;
 | |
|     if (frame < pal->frame())
 | |
|       break;
 | |
| 
 | |
|     found = pal;
 | |
|     if (frame == pal->frame())
 | |
|       break;
 | |
|   }
 | |
| 
 | |
|   ASSERT(found != NULL);
 | |
|   return found;
 | |
| }
 | |
| 
 | |
| const PalettesList& Sprite::getPalettes() const
 | |
| {
 | |
|   return m_palettes;
 | |
| }
 | |
| 
 | |
| void Sprite::setPalette(const Palette* pal, bool truncate)
 | |
| {
 | |
|   ASSERT(pal != NULL);
 | |
| 
 | |
|   if (!truncate) {
 | |
|     Palette* sprite_pal = palette(pal->frame());
 | |
|     pal->copyColorsTo(sprite_pal);
 | |
|   }
 | |
|   else {
 | |
|     Palette* other;
 | |
| 
 | |
|     PalettesList::iterator end = m_palettes.end();
 | |
|     PalettesList::iterator it = m_palettes.begin();
 | |
|     for (; it != end; ++it) {
 | |
|       other = *it;
 | |
| 
 | |
|       if (pal->frame() == other->frame()) {
 | |
|         pal->copyColorsTo(other);
 | |
|         return;
 | |
|       }
 | |
|       else if (pal->frame() < other->frame())
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     m_palettes.insert(it, new Palette(*pal));
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Sprite::resetPalettes()
 | |
| {
 | |
|   PalettesList::iterator end = m_palettes.end();
 | |
|   PalettesList::iterator it = m_palettes.begin();
 | |
| 
 | |
|   if (it != end) {
 | |
|     ++it;                       // Leave the first palette only.
 | |
|     while (it != end) {
 | |
|       delete *it;               // palette
 | |
|       it = m_palettes.erase(it);
 | |
|       end = m_palettes.end();
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| void Sprite::deletePalette(Palette* pal)
 | |
| {
 | |
|   ASSERT(pal != NULL);
 | |
| 
 | |
|   base::remove_from_container(m_palettes, pal);
 | |
|   delete pal;                   // palette
 | |
| }
 | |
| 
 | |
| RgbMap* Sprite::rgbMap(frame_t frame)
 | |
| {
 | |
|   int mask_color = (backgroundLayer() ? -1: transparentColor());
 | |
| 
 | |
|   if (m_rgbMap == NULL) {
 | |
|     m_rgbMap = new RgbMap();
 | |
|     m_rgbMap->regenerate(palette(frame), mask_color);
 | |
|   }
 | |
|   else if (!m_rgbMap->match(palette(frame))) {
 | |
|     m_rgbMap->regenerate(palette(frame), mask_color);
 | |
|   }
 | |
| 
 | |
|   return m_rgbMap;
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Frames
 | |
| 
 | |
| void Sprite::addFrame(frame_t newFrame)
 | |
| {
 | |
|   setTotalFrames(m_frames+1);
 | |
|   for (frame_t i=m_frames-1; i>=newFrame; --i)
 | |
|     setFrameDuration(i, frameDuration(i-1));
 | |
| 
 | |
|   folder()->displaceFrames(newFrame, +1);
 | |
| }
 | |
| 
 | |
| void Sprite::removeFrame(frame_t frame)
 | |
| {
 | |
|   folder()->displaceFrames(frame, -1);
 | |
| 
 | |
|   frame_t newTotal = m_frames-1;
 | |
|   for (frame_t i=frame; i<newTotal; ++i)
 | |
|     setFrameDuration(i, frameDuration(i+1));
 | |
|   setTotalFrames(newTotal);
 | |
| }
 | |
| 
 | |
| void Sprite::setTotalFrames(frame_t frames)
 | |
| {
 | |
|   frames = MAX(frame_t(1), frames);
 | |
|   m_frlens.resize(frames);
 | |
| 
 | |
|   if (frames > m_frames) {
 | |
|     for (frame_t c=m_frames; c<frames; ++c)
 | |
|       m_frlens[c] = m_frlens[m_frames-1];
 | |
|   }
 | |
| 
 | |
|   m_frames = frames;
 | |
| }
 | |
| 
 | |
| int Sprite::frameDuration(frame_t frame) const
 | |
| {
 | |
|   if (frame >= 0 && frame < m_frames)
 | |
|     return m_frlens[frame];
 | |
|   else
 | |
|     return 0;
 | |
| }
 | |
| 
 | |
| void Sprite::setFrameDuration(frame_t frame, int msecs)
 | |
| {
 | |
|   if (frame >= 0 && frame < m_frames)
 | |
|     m_frlens[frame] = MID(1, msecs, 65535);
 | |
| }
 | |
| 
 | |
| void Sprite::setFrameRangeDuration(frame_t from, frame_t to, int msecs)
 | |
| {
 | |
|   std::fill(
 | |
|     m_frlens.begin()+(std::size_t)from,
 | |
|     m_frlens.begin()+(std::size_t)to+1, MID(1, msecs, 65535));
 | |
| }
 | |
| 
 | |
| void Sprite::setDurationForAllFrames(int msecs)
 | |
| {
 | |
|   std::fill(m_frlens.begin(), m_frlens.end(), MID(1, msecs, 65535));
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Shared Images and CelData (for linked Cels)
 | |
| 
 | |
| ImageRef Sprite::getImageRef(ObjectId imageId)
 | |
| {
 | |
|   for (Cel* cel : cels()) {
 | |
|     if (cel->image()->id() == imageId)
 | |
|       return cel->imageRef();
 | |
|   }
 | |
|   return ImageRef(nullptr);
 | |
| }
 | |
| 
 | |
| CelDataRef Sprite::getCelDataRef(ObjectId celDataId)
 | |
| {
 | |
|   for (Cel* cel : cels()) {
 | |
|     if (cel->dataRef()->id() == celDataId)
 | |
|       return cel->dataRef();
 | |
|   }
 | |
|   return CelDataRef(nullptr);
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Images
 | |
| 
 | |
| void Sprite::replaceImage(ObjectId curImageId, const ImageRef& newImage)
 | |
| {
 | |
|   for (Cel* cel : cels()) {
 | |
|     if (cel->image()->id() == curImageId)
 | |
|       cel->data()->setImage(newImage);
 | |
|   }
 | |
| }
 | |
| 
 | |
| // TODO replace it with a images iterator
 | |
| void Sprite::getImages(std::vector<Image*>& images) const
 | |
| {
 | |
|   for (const auto& cel : uniqueCels())
 | |
|     images.push_back(cel->image());
 | |
| }
 | |
| 
 | |
| void Sprite::remapImages(frame_t frameFrom, frame_t frameTo, const std::vector<uint8_t>& mapping)
 | |
| {
 | |
|   ASSERT(m_format == IMAGE_INDEXED);
 | |
|   ASSERT(mapping.size() == 256);
 | |
| 
 | |
|   for (Cel* cel : cels()) {
 | |
|     // Remap this Cel because is inside the specified range
 | |
|     if (cel->frame() >= frameFrom &&
 | |
|         cel->frame() <= frameTo) {
 | |
|       Image* image = cel->image();
 | |
|       LockImageBits<IndexedTraits> bits(image);
 | |
|       LockImageBits<IndexedTraits>::iterator
 | |
|         it = bits.begin(),
 | |
|         end = bits.end();
 | |
| 
 | |
|       for (; it != end; ++it)
 | |
|         *it = mapping[*it];
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // Drawing
 | |
| 
 | |
| void Sprite::pickCels(int x, int y, frame_t frame, int opacityThreshold, CelList& cels) const
 | |
| {
 | |
|   std::vector<Layer*> layers;
 | |
|   getLayersList(layers);
 | |
| 
 | |
|   for (int i=(int)layers.size()-1; i>=0; --i) {
 | |
|     Layer* layer = layers[i];
 | |
|     if (!layer->isImage() || !layer->isVisible())
 | |
|       continue;
 | |
| 
 | |
|     Cel* cel = layer->cel(frame);
 | |
|     if (!cel)
 | |
|       continue;
 | |
| 
 | |
|     Image* image = cel->image();
 | |
|     if (!image)
 | |
|       continue;
 | |
| 
 | |
|     if (!cel->bounds().contains(gfx::Point(x, y)))
 | |
|       continue;
 | |
| 
 | |
|     color_t color = get_pixel(image,
 | |
|       x - cel->x(),
 | |
|       y - cel->y());
 | |
| 
 | |
|     bool isOpaque = true;
 | |
| 
 | |
|     switch (image->pixelFormat()) {
 | |
|       case IMAGE_RGB:
 | |
|         isOpaque = (rgba_geta(color) >= opacityThreshold);
 | |
|         break;
 | |
|       case IMAGE_INDEXED:
 | |
|         isOpaque = (color != image->maskColor());
 | |
|         break;
 | |
|       case IMAGE_GRAYSCALE:
 | |
|         isOpaque = (graya_geta(color) >= opacityThreshold);
 | |
|         break;
 | |
|     }
 | |
| 
 | |
|     if (!isOpaque)
 | |
|       continue;
 | |
| 
 | |
|     cels.push_back(cel);
 | |
|   }
 | |
|   fflush(stdout);
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| // CelsRange
 | |
| 
 | |
| CelsRange Sprite::cels() const
 | |
| {
 | |
|   return CelsRange(this, frame_t(0), lastFrame());
 | |
| }
 | |
| 
 | |
| CelsRange Sprite::cels(frame_t frame) const
 | |
| {
 | |
|   return CelsRange(this, frame, frame);
 | |
| }
 | |
| 
 | |
| CelsRange Sprite::uniqueCels() const
 | |
| {
 | |
|   return CelsRange(this, frame_t(0), lastFrame(), CelsRange::UNIQUE);
 | |
| }
 | |
| 
 | |
| //////////////////////////////////////////////////////////////////////
 | |
| 
 | |
| static Layer* index2layer(const Layer* layer, const LayerIndex& index, int* index_count)
 | |
| {
 | |
|   if (index == *index_count)
 | |
|     return (Layer*)layer;
 | |
|   else {
 | |
|     (*index_count)++;
 | |
| 
 | |
|     if (layer->isFolder()) {
 | |
|       Layer *found;
 | |
| 
 | |
|       LayerConstIterator it = static_cast<const LayerFolder*>(layer)->getLayerBegin();
 | |
|       LayerConstIterator end = static_cast<const LayerFolder*>(layer)->getLayerEnd();
 | |
| 
 | |
|       for (; it != end; ++it) {
 | |
|         if ((found = index2layer(*it, index, index_count)))
 | |
|           return found;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return NULL;
 | |
|   }
 | |
| }
 | |
| 
 | |
| static LayerIndex layer2index(const Layer* layer, const Layer* find_layer, int* index_count)
 | |
| {
 | |
|   if (layer == find_layer)
 | |
|     return LayerIndex(*index_count);
 | |
|   else {
 | |
|     (*index_count)++;
 | |
| 
 | |
|     if (layer->isFolder()) {
 | |
|       int found;
 | |
| 
 | |
|       LayerConstIterator it = static_cast<const LayerFolder*>(layer)->getLayerBegin();
 | |
|       LayerConstIterator end = static_cast<const LayerFolder*>(layer)->getLayerEnd();
 | |
| 
 | |
|       for (; it != end; ++it) {
 | |
|         if ((found = layer2index(*it, find_layer, index_count)) >= 0)
 | |
|           return LayerIndex(found);
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     return LayerIndex(-1);
 | |
|   }
 | |
| }
 | |
| 
 | |
| } // namespace doc
 |