1// Aseprite
2// Copyright (C) 2019-2020 Igara Studio S.A.
3// Copyright (C) 2001-2018 David Capello
4//
5// This program is distributed under the terms of
6// the End-User License Agreement for Aseprite.
7
8#ifdef HAVE_CONFIG_H
9#include "config.h"
10#endif
11
12#include "app/color_picker.h"
13
14#include "app/doc.h"
15#include "app/pref/preferences.h"
16#include "app/site.h"
17#include "app/util/wrap_point.h"
18#include "doc/cel.h"
19#include "doc/image.h"
20#include "doc/layer_tilemap.h"
21#include "doc/primitives.h"
22#include "doc/sprite.h"
23#include "doc/tileset.h"
24#include "gfx/point.h"
25#include "render/get_sprite_pixel.h"
26
27#define PICKER_TRACE(...) // TRACE
28
29namespace app {
30
31namespace {
32
33const int kOpacityThreshold = 1;
34
35bool get_cel_pixel(const Cel* cel,
36 const double x,
37 const double y,
38 const frame_t frame,
39 color_t& output)
40{
41 gfx::RectF celBounds;
42 if (cel->layer()->isReference())
43 celBounds = cel->boundsF();
44 else
45 celBounds = cel->bounds();
46
47 const doc::Image* image = cel->image();
48 gfx::PointF pos(x, y);
49 if (!celBounds.contains(pos))
50 return false;
51
52 // For tilemaps
53 if (image->pixelFormat() == IMAGE_TILEMAP) {
54 ASSERT(cel->layer()->isTilemap());
55
56 auto layerTilemap = static_cast<doc::LayerTilemap*>(cel->layer());
57 doc::Grid grid = layerTilemap->tileset()->grid();
58 grid.origin(grid.origin() + cel->position());
59
60 gfx::Point tilePos = grid.canvasToTile(gfx::Point(pos));
61 PICKER_TRACE("PICKER: tilePos=(%d %d)\n", tilePos.x,tilePos.y);
62 if (!image->bounds().contains(tilePos))
63 return false;
64
65 const doc::tile_index ti =
66 get_pixel(image, tilePos.x, tilePos.y);
67
68 PICKER_TRACE("PICKER: tile index=%d\n", ti);
69
70 doc::ImageRef tile = layerTilemap->tileset()->get(ti);
71 if (!tile)
72 return false;
73
74 const gfx::Point ipos =
75 gfx::Point(pos) - grid.tileToCanvas(tilePos);
76
77 PICKER_TRACE("PICKER: ipos=%d %d\n", ipos.x, ipos.y);
78
79 output = get_pixel(tile.get(), ipos.x, ipos.y);
80 PICKER_TRACE("PICKER: output=%d\n", output);
81 return true;
82 }
83 // Regular images
84 else {
85 pos.x = (pos.x-celBounds.x)*image->width()/celBounds.w;
86 pos.y = (pos.y-celBounds.y)*image->height()/celBounds.h;
87 const gfx::Point ipos(pos);
88 if (!image->bounds().contains(ipos))
89 return false;
90
91 output = get_pixel(image, ipos.x, ipos.y);
92 return true;
93 }
94}
95
96}
97
98ColorPicker::ColorPicker()
99 : m_tile(doc::notile)
100 , m_alpha(0)
101 , m_layer(nullptr)
102{
103}
104
105void ColorPicker::pickColor(const Site& site,
106 const gfx::PointF& _pos,
107 const render::Projection& proj,
108 const Mode mode)
109{
110 const doc::Sprite* sprite = site.sprite();
111 gfx::PointF pos = _pos;
112
113 m_alpha = 255;
114 m_color = app::Color::fromMask();
115
116 // Check tiled mode
117 if (sprite && site.document()) {
118 auto doc = static_cast<const Doc*>(site.document());
119 DocumentPreferences& docPref = Preferences::instance().document(doc);
120
121 pos = wrap_pointF(docPref.tiled.mode(),
122 site.sprite()->size(), pos);
123 }
124
125 // Get the color from the image
126 switch (mode) {
127
128 // Pick from the composed image
129 case FromComposition: {
130 doc::CelList cels;
131 sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
132 sprite->allVisibleLayers(), cels);
133 if (!cels.empty())
134 m_layer = cels.front()->layer();
135
136 if (site.tilemapMode() == TilemapMode::Tiles) {
137 if (cels.empty() || !cels.front()->image()->isTilemap())
138 return;
139
140 const gfx::Point tilePos = site.grid().canvasToTile(gfx::Point(pos));
141 if (cels.front()->image()->bounds().contains(tilePos)) {
142 m_tile = doc::get_pixel(cels.front()->image(), tilePos.x, tilePos.y);
143 m_color = app::Color::fromIndex(m_tile);
144 }
145 }
146 else if (site.tilemapMode() == TilemapMode::Pixels) {
147 m_color = app::Color::fromImage(
148 sprite->pixelFormat(),
149 render::get_sprite_pixel(sprite, pos.x, pos.y,
150 site.frame(), proj,
151 Preferences::instance().experimental.newBlend()));
152 }
153 break;
154 }
155
156 // Pick from the current layer
157 case FromActiveLayer: {
158 const Cel* cel = site.cel();
159 if (!cel)
160 return;
161
162 if (site.tilemapMode() == TilemapMode::Tiles) {
163 const gfx::Point tilePos = site.grid().canvasToTile(gfx::Point(pos));
164 if (cel->image()->bounds().contains(tilePos)) {
165 m_tile = doc::get_pixel(cel->image(), tilePos.x, tilePos.y);
166 m_color = app::Color::fromIndex(m_tile);
167 }
168 }
169 else if (site.tilemapMode() == TilemapMode::Pixels) {
170 doc::color_t imageColor;
171 if (!get_cel_pixel(cel, pos.x, pos.y,
172 site.frame(), imageColor))
173 return;
174
175 doc::PixelFormat pixelFormat =
176 (cel->layer()->isTilemap() ? sprite->pixelFormat():
177 cel->image()->pixelFormat());
178 switch (pixelFormat) {
179 case IMAGE_RGB:
180 m_alpha = doc::rgba_geta(imageColor);
181 break;
182 case IMAGE_GRAYSCALE:
183 m_alpha = doc::graya_geta(imageColor);
184 break;
185 }
186
187 m_color = app::Color::fromImage(pixelFormat, imageColor);
188 m_layer = const_cast<Layer*>(site.layer());
189 }
190 break;
191 }
192
193 case FromFirstReferenceLayer: {
194 doc::CelList cels;
195 sprite->pickCels(pos.x, pos.y, site.frame(), kOpacityThreshold,
196 sprite->allVisibleReferenceLayers(), cels);
197
198 for (const Cel* cel : cels) {
199 doc::color_t imageColor;
200 if (get_cel_pixel(cel, pos.x, pos.y,
201 site.frame(), imageColor)) {
202 m_color = app::Color::fromImage(
203 cel->image()->pixelFormat(), imageColor);
204 m_layer = cel->layer();
205 break;
206 }
207 }
208 break;
209 }
210 }
211}
212
213} // namespace app
214