mirror of
https://github.com/aseprite/aseprite.git
synced 2025-03-23 22:43:32 +00:00
Merge duplicated samples when -sheet-pack is used (fix #1316)
This commit is contained in:
parent
01821ed989
commit
51be644bf4
@ -20,6 +20,7 @@
|
||||
#include "app/pref/preferences.h"
|
||||
#include "app/restore_visible_layers.h"
|
||||
#include "app/snap_to_grid.h"
|
||||
#include "doc/images_map.h"
|
||||
#include "base/convert_to.h"
|
||||
#include "base/fs.h"
|
||||
#include "base/fstream_path.h"
|
||||
@ -28,6 +29,7 @@
|
||||
#include "doc/algorithm/shrink_bounds.h"
|
||||
#include "doc/cel.h"
|
||||
#include "doc/image.h"
|
||||
#include "doc/images_map.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/palette.h"
|
||||
#include "doc/primitives.h"
|
||||
@ -47,8 +49,8 @@
|
||||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <iostream>
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#define DX_TRACE(...) // TRACEARGS
|
||||
|
||||
@ -182,8 +184,9 @@ public:
|
||||
m_filename(filename),
|
||||
m_innerPadding(innerPadding),
|
||||
m_extrude(extrude),
|
||||
m_bounds(new SampleBounds(sprite)),
|
||||
m_isDuplicated(false) {
|
||||
m_isLinked(false),
|
||||
m_isDuplicated(false),
|
||||
m_bounds(new SampleBounds(sprite)) {
|
||||
}
|
||||
|
||||
Doc* document() const { return m_document; }
|
||||
@ -216,15 +219,78 @@ public:
|
||||
void setTrimmedBounds(const gfx::Rect& bounds) { m_bounds->setTrimmedBounds(bounds); }
|
||||
void setInTextureBounds(const gfx::Rect& bounds) { m_bounds->setInTextureBounds(bounds); }
|
||||
|
||||
bool isLinked() const { return m_isLinked; }
|
||||
bool isDuplicated() const { return m_isDuplicated; }
|
||||
bool isEmpty() const { return m_bounds->trimmedBounds().isEmpty(); }
|
||||
bool isEmpty() const {
|
||||
// TODO trimmed bounds cannot be empty now (samples that are
|
||||
// completely trimmed out are included as a sample of size 1x1)
|
||||
ASSERT(!m_bounds->trimmedBounds().isEmpty());
|
||||
return m_bounds->trimmedBounds().isEmpty();
|
||||
}
|
||||
SampleBoundsPtr sharedBounds() const { return m_bounds; }
|
||||
|
||||
void setLinked() { m_isLinked = true; }
|
||||
void setDuplicated() { m_isDuplicated = true; }
|
||||
|
||||
void setSharedBounds(const SampleBoundsPtr& bounds) {
|
||||
m_isDuplicated = true;
|
||||
m_bounds = bounds;
|
||||
}
|
||||
|
||||
ImageRef createRender(ImageBufferPtr& imageBuf) {
|
||||
ASSERT(m_sprite);
|
||||
|
||||
ImageRef render(
|
||||
Image::create(m_sprite->pixelFormat(),
|
||||
m_sprite->width(),
|
||||
m_sprite->height(),
|
||||
imageBuf));
|
||||
render->setMaskColor(m_sprite->transparentColor());
|
||||
clear_image(render.get(), m_sprite->transparentColor());
|
||||
renderSample(render.get(), 0, 0, false);
|
||||
return render;
|
||||
}
|
||||
|
||||
void renderSample(doc::Image* dst, int x, int y, bool extrude) const {
|
||||
RestoreVisibleLayers layersVisibility;
|
||||
if (m_selLayers)
|
||||
layersVisibility.showSelectedLayers(m_sprite,
|
||||
*m_selLayers);
|
||||
|
||||
render::Render render;
|
||||
render.setNewBlend(Preferences::instance().experimental.newBlend());
|
||||
|
||||
if (extrude) {
|
||||
const gfx::Rect& trim = trimmedBounds();
|
||||
|
||||
// Displaced position onto the destination texture
|
||||
int dx[] = { 0, 1, trim.w+1 };
|
||||
int dy[] = { 0, 1, trim.h+1 };
|
||||
|
||||
// Starting point of the area to be copied from the original image
|
||||
// taking into account the size of the trimmed sprite
|
||||
int srcx[] = { trim.x, trim.x, trim.x2()-1 };
|
||||
int srcy[] = { trim.y, trim.y, trim.y2()-1 };
|
||||
|
||||
// Size of the area to be copied from original image, starting at
|
||||
// the point (srcx[i], srxy[j])
|
||||
int szx[] = { 1, trim.w, 1 };
|
||||
int szy[] = { 1, trim.h, 1 };
|
||||
|
||||
// Render a 9-patch image extruding the sample one pixel on each
|
||||
// side.
|
||||
for (int j=0; j<3; ++j) {
|
||||
for (int i=0; i<3; ++i) {
|
||||
gfx::Clip clip(x+dx[i], y+dy[j], gfx::RectT<int>(srcx[i], srcy[j], szx[i], szy[j]));
|
||||
render.renderSprite(dst, m_sprite, m_frame, clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
gfx::Clip clip(x, y, trimmedBounds());
|
||||
render.renderSprite(dst, m_sprite, m_frame, clip);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
Doc* m_document;
|
||||
Sprite* m_sprite;
|
||||
@ -233,13 +299,14 @@ private:
|
||||
std::string m_filename;
|
||||
int m_innerPadding;
|
||||
bool m_extrude;
|
||||
SampleBoundsPtr m_bounds;
|
||||
bool m_isLinked;
|
||||
bool m_isDuplicated;
|
||||
SampleBoundsPtr m_bounds;
|
||||
};
|
||||
|
||||
class DocExporter::Samples {
|
||||
public:
|
||||
typedef std::list<Sample> List;
|
||||
typedef std::vector<Sample> List;
|
||||
typedef List::iterator iterator;
|
||||
typedef List::const_iterator const_iterator;
|
||||
|
||||
@ -249,6 +316,10 @@ public:
|
||||
m_samples.push_back(sample);
|
||||
}
|
||||
|
||||
const Sample& operator[](const size_t i) const {
|
||||
return m_samples[i];
|
||||
}
|
||||
|
||||
iterator begin() { return m_samples.begin(); }
|
||||
iterator end() { return m_samples.end(); }
|
||||
const_iterator begin() const { return m_samples.begin(); }
|
||||
@ -264,8 +335,7 @@ public:
|
||||
virtual void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) = 0;
|
||||
};
|
||||
|
||||
class DocExporter::SimpleLayoutSamples :
|
||||
public DocExporter::LayoutSamples {
|
||||
class DocExporter::SimpleLayoutSamples : public DocExporter::LayoutSamples {
|
||||
public:
|
||||
SimpleLayoutSamples(SpriteSheetType type)
|
||||
: m_type(type) {
|
||||
@ -279,7 +349,7 @@ public:
|
||||
gfx::Size rowSize(0, 0);
|
||||
|
||||
for (auto& sample : samples) {
|
||||
if (sample.isDuplicated())
|
||||
if (sample.isLinked())
|
||||
continue;
|
||||
|
||||
if (sample.isEmpty()) {
|
||||
@ -355,18 +425,37 @@ private:
|
||||
SpriteSheetType m_type;
|
||||
};
|
||||
|
||||
class DocExporter::BestFitLayoutSamples :
|
||||
public DocExporter::LayoutSamples {
|
||||
class DocExporter::BestFitLayoutSamples : public DocExporter::LayoutSamples {
|
||||
ImageBufferPtr m_imageBuf;
|
||||
public:
|
||||
BestFitLayoutSamples(ImageBufferPtr& buf)
|
||||
: m_imageBuf(buf) {
|
||||
}
|
||||
void layoutSamples(Samples& samples, int borderPadding, int shapePadding, int& width, int& height) override {
|
||||
gfx::PackingRects pr(borderPadding, shapePadding);
|
||||
doc::ImagesMap duplicates;
|
||||
|
||||
uint32_t i = 0;
|
||||
for (auto& sample : samples) {
|
||||
if (sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
if (sample.isLinked() ||
|
||||
sample.isEmpty()) {
|
||||
++i;
|
||||
continue;
|
||||
}
|
||||
|
||||
pr.add(sample.requiredSize());
|
||||
ImageRef sampleRender(sample.createRender(m_imageBuf));
|
||||
auto it = duplicates.find(sampleRender);
|
||||
if (it != duplicates.end()) {
|
||||
const uint32_t j = it->second;
|
||||
|
||||
sample.setDuplicated();
|
||||
sample.setSharedBounds(samples[j].sharedBounds());
|
||||
}
|
||||
else {
|
||||
duplicates[sampleRender] = i;
|
||||
pr.add(sample.requiredSize());
|
||||
}
|
||||
++i;
|
||||
}
|
||||
|
||||
if (width == 0 || height == 0) {
|
||||
@ -379,7 +468,8 @@ public:
|
||||
|
||||
auto it = pr.begin();
|
||||
for (auto& sample : samples) {
|
||||
if (sample.isDuplicated() ||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
continue;
|
||||
|
||||
@ -544,8 +634,9 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
if (other.sprite() == sprite &&
|
||||
other.layer() == layer &&
|
||||
other.frame() == link->frame()) {
|
||||
ASSERT(!other.isDuplicated());
|
||||
ASSERT(!other.isLinked());
|
||||
|
||||
sample.setLinked();
|
||||
sample.setSharedBounds(other.sharedBounds());
|
||||
done = true;
|
||||
break;
|
||||
@ -561,15 +652,7 @@ void DocExporter::captureSamples(Samples& samples)
|
||||
if (layer && layer->isImage() && !cel && m_ignoreEmptyCels)
|
||||
continue;
|
||||
|
||||
std::unique_ptr<Image> sampleRender(
|
||||
Image::create(sprite->pixelFormat(),
|
||||
sprite->width(),
|
||||
sprite->height(),
|
||||
m_sampleRenderBuf));
|
||||
|
||||
sampleRender->setMaskColor(sprite->transparentColor());
|
||||
clear_image(sampleRender.get(), sprite->transparentColor());
|
||||
renderSample(sample, sampleRender.get(), 0, 0, false);
|
||||
ImageRef sampleRender(sample.createRender(m_sampleRenderBuf));
|
||||
|
||||
gfx::Rect frameBounds;
|
||||
doc::color_t refColor = 0;
|
||||
@ -645,7 +728,7 @@ void DocExporter::layoutSamples(Samples& samples)
|
||||
{
|
||||
switch (m_sheetType) {
|
||||
case SpriteSheetType::Packed: {
|
||||
BestFitLayoutSamples layout;
|
||||
BestFitLayoutSamples layout(m_sampleRenderBuf);
|
||||
layout.layoutSamples(
|
||||
samples, m_borderPadding, m_shapePadding,
|
||||
m_textureWidth, m_textureHeight);
|
||||
@ -666,7 +749,8 @@ gfx::Size DocExporter::calculateSheetSize(const Samples& samples) const
|
||||
gfx::Rect fullTextureBounds(0, 0, m_textureWidth, m_textureHeight);
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
if (sample.isDuplicated() ||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
continue;
|
||||
|
||||
@ -701,7 +785,8 @@ Doc* DocExporter::createEmptyTexture(const Samples& samples) const
|
||||
color_t transparentColor = 0;
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
if (sample.isDuplicated() ||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
continue;
|
||||
|
||||
@ -757,7 +842,8 @@ void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* tex
|
||||
textureImage->clear(0);
|
||||
|
||||
for (const auto& sample : samples) {
|
||||
if (sample.isDuplicated() ||
|
||||
if (sample.isLinked() ||
|
||||
sample.isDuplicated() ||
|
||||
sample.isEmpty())
|
||||
continue;
|
||||
|
||||
@ -772,7 +858,8 @@ void DocExporter::renderTexture(Context* ctx, const Samples& samples, Image* tex
|
||||
.execute(ctx);
|
||||
}
|
||||
|
||||
renderSample(sample, textureImage,
|
||||
sample.renderSample(
|
||||
textureImage,
|
||||
sample.inTextureBounds().x+m_innerPadding,
|
||||
sample.inTextureBounds().y+m_innerPadding,
|
||||
m_extrude);
|
||||
@ -1034,46 +1121,4 @@ void DocExporter::createDataFile(const Samples& samples, std::ostream& os, Image
|
||||
<< "}\n";
|
||||
}
|
||||
|
||||
void DocExporter::renderSample(const Sample& sample, doc::Image* dst, int x, int y, bool extrude) const
|
||||
{
|
||||
RestoreVisibleLayers layersVisibility;
|
||||
if (sample.selectedLayers())
|
||||
layersVisibility.showSelectedLayers(sample.sprite(),
|
||||
*sample.selectedLayers());
|
||||
|
||||
render::Render render;
|
||||
render.setNewBlend(Preferences::instance().experimental.newBlend());
|
||||
|
||||
if (extrude) {
|
||||
const gfx::Rect& trim = sample.trimmedBounds();
|
||||
|
||||
// Displaced position onto the destination texture
|
||||
int dx[] = {0, 1, trim.w+1};
|
||||
int dy[] = {0, 1, trim.h+1};
|
||||
|
||||
// Starting point of the area to be copied from the original image
|
||||
// taking into account the size of the trimmed sprite
|
||||
int srcx[] = {trim.x, trim.x, trim.x2()-1};
|
||||
int srcy[] = {trim.y, trim.y, trim.y2()-1};
|
||||
|
||||
// Size of the area to be copied from original image, starting at
|
||||
// the point (srcx[i], srxy[j])
|
||||
int szx[] = {1, trim.w, 1};
|
||||
int szy[] = {1, trim.h, 1};
|
||||
|
||||
// Render a 9-patch image extruding the sample one pixel on each
|
||||
// side.
|
||||
for(int j=0; j<3; ++j) {
|
||||
for(int i=0; i<3; ++i) {
|
||||
gfx::Clip clip(x+dx[i], y+dy[j], gfx::RectT<int>(srcx[i], srcy[j], szx[i], szy[j]));
|
||||
render.renderSprite(dst, sample.sprite(), sample.frame(), clip);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
gfx::Clip clip(x, y, sample.trimmedBounds());
|
||||
render.renderSprite(dst, sample.sprite(), sample.frame(), clip);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace app
|
||||
|
@ -102,7 +102,6 @@ namespace app {
|
||||
Doc* createEmptyTexture(const Samples& samples) const;
|
||||
void renderTexture(Context* ctx, const Samples& samples, doc::Image* textureImage) const;
|
||||
void createDataFile(const Samples& samples, std::ostream& os, doc::Image* textureImage);
|
||||
void renderSample(const Sample& sample, doc::Image* dst, int x, int y, bool extrude) const;
|
||||
|
||||
class Item {
|
||||
public:
|
||||
|
@ -21,6 +21,7 @@
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_impl.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/images_map.h"
|
||||
#include "doc/layer.h"
|
||||
#include "doc/mask.h"
|
||||
#include "doc/object.h"
|
||||
|
42
src/doc/images_map.h
Normal file
42
src/doc/images_map.h
Normal file
@ -0,0 +1,42 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (C) 2019 Igara Studio S.A.
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
// Read LICENSE.txt for more information.
|
||||
|
||||
#ifndef DOC_IMAGES_MAP_H_INCLUDED
|
||||
#define DOC_IMAGES_MAP_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "doc/image.h"
|
||||
#include "doc/image_ref.h"
|
||||
#include "doc/primitives.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
namespace doc {
|
||||
namespace details {
|
||||
|
||||
struct image_hash {
|
||||
size_t operator()(const ImageRef& i) const {
|
||||
return calculate_image_hash(i.get(), i->bounds());
|
||||
}
|
||||
};
|
||||
|
||||
struct image_eq {
|
||||
bool operator()(const ImageRef& a, const ImageRef& b) const {
|
||||
return is_same_image(a.get(), b.get());
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
// A hash table used to match Image pixels data <-> an index
|
||||
typedef std::unordered_map<ImageRef,
|
||||
uint32_t,
|
||||
details::image_hash,
|
||||
details::image_eq> ImagesMap;
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -416,4 +416,49 @@ void remap_image(Image* image, const Remap& remap)
|
||||
*it = remap[*it];
|
||||
}
|
||||
|
||||
// TODO test this hash routine and find a better alternative
|
||||
|
||||
template <typename ImageTraits, uint32_t Mask>
|
||||
static uint32_t calculate_image_hash_templ(const Image* image,
|
||||
const gfx::Rect& bounds)
|
||||
{
|
||||
uint32_t hash = 0;
|
||||
for (int y=0; y<bounds.h; ++y) {
|
||||
auto p = (typename ImageTraits::address_t)image->getPixelAddress(bounds.x, bounds.y+y);
|
||||
for (int x=0; x<bounds.w; ++x, ++p) {
|
||||
uint32_t value = *p;
|
||||
uint32_t mask = Mask;
|
||||
while (mask) {
|
||||
hash += value & mask & 0xff;
|
||||
hash <<= 1;
|
||||
value >>= 8;
|
||||
mask >>= 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
uint32_t calculate_image_hash(const Image* img, const gfx::Rect& bounds)
|
||||
{
|
||||
switch (img->pixelFormat()) {
|
||||
case IMAGE_RGB: return calculate_image_hash_templ<RgbTraits, rgba_rgb_mask>(img, bounds);
|
||||
case IMAGE_GRAYSCALE: return calculate_image_hash_templ<GrayscaleTraits, graya_v_mask>(img, bounds);
|
||||
case IMAGE_INDEXED: return calculate_image_hash_templ<IndexedTraits, 0xff>(img, bounds);
|
||||
case IMAGE_BITMAP: return calculate_image_hash_templ<BitmapTraits, 1>(img, bounds);
|
||||
}
|
||||
|
||||
uint32_t hash = 0;
|
||||
for (int y=0; y<bounds.h; ++y) {
|
||||
int bytes = img->getRowStrideSize(bounds.w);
|
||||
uint8_t* p = img->getPixelAddress(bounds.x, bounds.y+y);
|
||||
while (bytes-- > 0) {
|
||||
hash += *p;
|
||||
hash <<= 1;
|
||||
++p;
|
||||
}
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
} // namespace doc
|
||||
|
@ -1,5 +1,5 @@
|
||||
// Aseprite Document Library
|
||||
// Copyright (c) 2018 Igara Studio S.A.
|
||||
// Copyright (c) 2018-2019 Igara Studio S.A.
|
||||
// Copyright (c) 2001-2016 David Capello
|
||||
//
|
||||
// This file is released under the terms of the MIT license.
|
||||
@ -9,6 +9,7 @@
|
||||
#define DOC_PRIMITIVES_H_INCLUDED
|
||||
#pragma once
|
||||
|
||||
#include "base/ints.h"
|
||||
#include "doc/color.h"
|
||||
#include "doc/image_buffer.h"
|
||||
#include "gfx/fwd.h"
|
||||
@ -48,6 +49,9 @@ namespace doc {
|
||||
|
||||
void remap_image(Image* image, const Remap& remap);
|
||||
|
||||
uint32_t calculate_image_hash(const Image* image,
|
||||
const gfx::Rect& bounds);
|
||||
|
||||
} // namespace doc
|
||||
|
||||
#endif
|
||||
|
Loading…
x
Reference in New Issue
Block a user