|
#include <Siv3D.hpp> // OpenSiv3D v0.4.3 |
|
|
|
struct AnimationImage |
|
{ |
|
Array<Image> images; |
|
Array<Texture> textures_prev; |
|
|
|
// フレームの時間 |
|
Array<int32> delays; |
|
|
|
int32 duration = 0; |
|
|
|
explicit operator bool() const noexcept |
|
{ |
|
return !images.isEmpty(); |
|
} |
|
|
|
Size size() const noexcept |
|
{ |
|
if (!images) |
|
{ |
|
return Size(0, 0); |
|
} |
|
|
|
return images.front().size(); |
|
} |
|
|
|
size_t frames() const noexcept |
|
{ |
|
return images.size(); |
|
} |
|
|
|
size_t getFrameIndex(int32 timeMillisec) const noexcept |
|
{ |
|
return AnimatedGIFReader::MillisecToIndex(timeMillisec, delays, duration); |
|
} |
|
|
|
const Image& getImage(int32 timeMillisec) const noexcept |
|
{ |
|
return images[getFrameIndex(timeMillisec)]; |
|
} |
|
|
|
const Texture& getPreviewTexture(int32 timeMillisec) const noexcept |
|
{ |
|
return textures_prev[getFrameIndex(timeMillisec)]; |
|
} |
|
}; |
|
|
|
void Main() |
|
{ |
|
Window::SetTitle(U"Picture Brush Paint"); |
|
|
|
DragDrop::AcceptFilePaths(true); |
|
|
|
const Font font(24); |
|
|
|
Image original; |
|
Texture previewTex; |
|
{ |
|
Image prev(100, 100, Palette::Gray); |
|
previewTex = Texture(prev); |
|
} |
|
Image brushPic; |
|
constexpr Size canvasSize(600, 600); |
|
Image image(canvasSize, Palette::White); |
|
DynamicTexture tex(image); |
|
|
|
Point from = Point(0, 0); |
|
Point to = Point(0, 0); |
|
|
|
double gap = 0.4 / 0.9; |
|
double scale = 1.0; |
|
|
|
Optional<AnimationImage> animation; |
|
|
|
while (System::Update()) |
|
{ |
|
// Animation |
|
const int32 timeMillisec = static_cast<int32>(Scene::Time() * 1000); |
|
if (animation) |
|
{ |
|
brushPic = animation->getImage(timeMillisec).scaled(scale); |
|
} |
|
|
|
// File Dropped |
|
if (DragDrop::HasNewFilePaths()) |
|
{ |
|
String path = DragDrop::GetDroppedFilePaths()[0].path; |
|
String ext = FileSystem::Extension(path); |
|
|
|
animation = none; |
|
original = Image(); |
|
|
|
if (ext == U"gif") |
|
{ |
|
// GIF ファイルを開く |
|
const AnimatedGIFReader gif(path); |
|
|
|
if (gif) |
|
{ |
|
Array<Image> images; |
|
animation = AnimationImage(); |
|
|
|
// GIF アニメーションを読み込み |
|
if (gif.read(images, animation->delays, animation->duration)) |
|
{ |
|
animation->images = Array<Image>(images); |
|
|
|
double s = images.front().width() > images.front().height() ? 100.0 / images.front().width() : 100.0 / images.front().height(); |
|
Point p = { (int)(50 - images.front().width() * s / 2.0), (int)(50 - images.front().height() * s / 2.0) }; |
|
animation->textures_prev = images.map([s, p](const Image& i) |
|
{ |
|
Image prev(100, 100, Palette::Gray); |
|
i.scaled(s).paint(prev, p); |
|
return Texture(prev); |
|
}); |
|
} |
|
else |
|
{ |
|
animation = none; |
|
} |
|
} |
|
} |
|
|
|
if (ext == U"png" || ext == U"jpeg" || ext == U"jpg" || (!animation && ext == U"gif")) |
|
{ |
|
original = Image(path); |
|
brushPic = original.scaled(scale); |
|
|
|
Image prev(100, 100, Palette::Gray); |
|
double s = original.width() > original.height() ? 100.0 / original.width() : 100.0 / original.height(); |
|
Point p = { (int)(50 - original.width() * s / 2.0), (int)(50 - original.height() * s / 2.0) }; |
|
original.scaled(s).paint(prev, p); |
|
previewTex = Texture(prev); |
|
} |
|
} |
|
|
|
// MouseL pressed |
|
if (MouseL.down()) |
|
{ |
|
from = Cursor::Pos(); |
|
|
|
if (animation || original) |
|
{ |
|
brushPic.paint(image, from.movedBy(-brushPic.size() / 2)); |
|
tex.fill(image); |
|
} |
|
} |
|
|
|
// MouseL pressing |
|
if (MouseL.pressed()) |
|
{ |
|
to = Cursor::Pos(); |
|
|
|
if (animation || original) |
|
{ |
|
double dist = from.distanceFrom(to); |
|
double l = Min(brushPic.width(), brushPic.height()) * (0.9 * gap + 0.1); |
|
|
|
if (dist > l) |
|
{ |
|
brushPic.paint(image, to.movedBy(-brushPic.size() / 2)); |
|
from = to; |
|
|
|
tex.fill(image); |
|
} |
|
} |
|
} |
|
|
|
// Clear |
|
if (SimpleGUI::Button(U"Clear", Vec2(640, 20), 120)) |
|
{ |
|
image.fill(Palette::White); |
|
tex.fill(image); |
|
} |
|
|
|
// Save |
|
if (SimpleGUI::Button(U"Save", Vec2(640, 70), 120)) |
|
{ |
|
auto path = Dialog::SaveImage(); |
|
if (path) |
|
{ |
|
image.savePNG(path.value()); |
|
} |
|
} |
|
|
|
// Gap |
|
font(U"gap:").draw(Vec2(640, 125)); |
|
font((0.9 * gap + 0.1)).draw(Vec2(640, 155)); |
|
SimpleGUI::Slider(gap, Vec2(640, 190), 120); |
|
|
|
// Scale |
|
font(U"scale:").draw(Vec2(640, 240)); |
|
font((0.9 * scale + 0.1)).draw(Vec2(640, 265)); |
|
if (SimpleGUI::Slider(scale, Vec2(640, 300), 120)) |
|
{ |
|
if (original) |
|
{ |
|
brushPic = original.scaled(scale); |
|
} |
|
} |
|
|
|
// Brush preview |
|
font(U"brush:").draw(Vec2(640, 380)); |
|
if (animation) |
|
{ |
|
Rect(650, 420, 100, 100)(animation->getPreviewTexture(timeMillisec)).draw(); |
|
} |
|
else |
|
{ |
|
Rect(650, 420, 100, 100)(previewTex).draw(); |
|
} |
|
|
|
tex.draw(); |
|
} |
|
|
|
} |