1 // -*- mode: c++; c-basic-offset: 4; indent-tabs-mode: nil; -*-
2 // (c) 2016 Henner Zeller <h.zeller@acm.org>
3 //
4 // This program is free software; you can redistribute it and/or modify
5 // it under the terms of the GNU General Public License as published by
6 // the Free Software Foundation version 2.
7 //
8 // This program is distributed in the hope that it will be useful,
9 // but WITHOUT ANY WARRANTY; without even the implied warranty of
10 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11 // GNU General Public License for more details.
12 //
13 // You should have received a copy of the GNU General Public License
14 // along with this program.  If not, see <http://gnu.org/licenses/gpl-2.0.txt>
15 
16 #include "image-display.h"
17 
18 #include "terminal-canvas.h"
19 #include "timg-time.h"
20 
21 #include <Magick++.h>
22 #include <assert.h>
23 #include <fcntl.h>
24 #include <math.h>
25 #include <string.h>
26 #include <sys/stat.h>
27 #include <sys/types.h>
28 #include <unistd.h>
29 
30 #include <algorithm>
31 
32 static constexpr bool kDebug = false;
33 
34 namespace timg {
CopyToFramebuffer(const Magick::Image & img,timg::Framebuffer * result)35 static void CopyToFramebuffer(const Magick::Image &img,
36                               timg::Framebuffer *result) {
37     assert(result->width() >= (int) img.columns()
38            && result->height() >= (int) img.rows());
39     for (size_t y = 0; y < img.rows(); ++y) {
40         for (size_t x = 0; x < img.columns(); ++x) {
41             const Magick::Color &c = img.pixelColor(x, y);
42             result->SetPixel(x, y,
43                              {
44                                  ScaleQuantumToChar(c.redQuantum()),
45                                  ScaleQuantumToChar(c.greenQuantum()),
46                                  ScaleQuantumToChar(c.blueQuantum()),
47                                  (uint8_t)(0xff - ScaleQuantumToChar(
48                                                c.alphaQuantum()))
49                              });
50         }
51     }
52 }
53 
54 // Frame already prepared as the buffer to be sent, so copy to terminal-buffer
55 // does not have to be done online. Also knows about the animation delay.
56 class ImageLoader::PreprocessedFrame {
57 public:
PreprocessedFrame(const Magick::Image & img,const DisplayOptions & opt,bool is_part_of_animation)58     PreprocessedFrame(const Magick::Image &img, const DisplayOptions &opt,
59                       bool is_part_of_animation)
60         : delay_(DurationFromImgDelay(img, is_part_of_animation)),
61           framebuffer_(img.columns(), img.rows()) {
62         CopyToFramebuffer(img, &framebuffer_);
63         framebuffer_.AlphaComposeBackground(opt.bgcolor_getter,
64                                             opt.bg_pattern_color,
65                                             opt.pattern_size * opt.cell_x_px,
66                                             opt.pattern_size * opt.cell_y_px/2);
67     }
delay() const68     Duration delay() const { return delay_; }
framebuffer() const69     const timg::Framebuffer &framebuffer() const { return framebuffer_; }
70 
71 private:
DurationFromImgDelay(const Magick::Image & img,bool is_part_of_animation)72     static Duration DurationFromImgDelay(const Magick::Image &img,
73                                          bool is_part_of_animation) {
74         if (!is_part_of_animation) return Duration::Millis(0);
75         int delay_time = img.animationDelay();  // in 1/100s of a second.
76         if (delay_time < 1) delay_time = 10;
77         return Duration::Millis(delay_time * 10);
78     }
79     const Duration delay_;
80     timg::Framebuffer framebuffer_;
81 };
82 
~ImageLoader()83 ImageLoader::~ImageLoader() {
84     for (PreprocessedFrame *f : frames_) delete f;
85 }
86 
VersionInfo()87 const char *ImageLoader::VersionInfo() {
88     return "GraphicsMagick " MagickLibVersionText " (" MagickReleaseDate ")";
89 }
90 
EndsWith(const std::string & filename,const char * suffix)91 static bool EndsWith(const std::string &filename, const char *suffix) {
92     const size_t flen = filename.length();
93     const size_t slen = strlen(suffix);
94     if (flen < slen) return false;
95     return strcasecmp(filename.c_str() + flen - slen, suffix) == 0;
96 }
97 
98 struct ExifImageOp { int angle = 0; bool flip = false; };
GetExifOp(Magick::Image & img)99 static ExifImageOp GetExifOp(Magick::Image &img) {
100     const std::string rotation_tag = img.attribute("EXIF:Orientation");
101     if (rotation_tag.empty() || rotation_tag.size() != 1)
102         return {}; // Nothing to do or broken tag.
103     switch (rotation_tag[0]) {
104     case '2': return { 180, true  };
105     case '3': return { 180, false };
106     case '4': return {   0, true  };
107     case '5': return {  90, true  };
108     case '6': return {  90, false };
109     case '7': return { -90, true  };
110     case '8': return { -90, false };
111     }
112     return {};
113 }
114 
LooksLikeApng(const std::string & filename)115 static bool LooksLikeApng(const std::string &filename) {
116     // Somewhat handwavy: the "acTL" chunk could of course be at other places as well,
117     // let's assume it is just after IHDR.
118     int fd = open(filename.c_str(), O_RDONLY);
119     if (fd < 0) return false;
120     char actl_sig[4] = {};
121     static constexpr int kPngHeaderLen = 8;
122     static constexpr int kPngIHDRLen = 8 + 13 + 4;
123     const ssize_t len = pread(fd, actl_sig, 4, kPngHeaderLen + kPngIHDRLen + 4);
124     close(fd);
125     return len == 4 && memcmp(actl_sig, "acTL", 4) == 0;
126 }
127 
LoadAndScale(const DisplayOptions & opts,int frame_offset,int frame_count)128 bool ImageLoader::LoadAndScale(const DisplayOptions &opts,
129                                int frame_offset, int frame_count) {
130     options_ = opts;
131 
132     const char *const file = filename().c_str();
133     for (const char *ending : { ".png", ".apng" }) {
134         if (strcasecmp(file + strlen(file) - strlen(ending), ending) == 0 &&
135             LooksLikeApng(filename())) {
136             return false;   // ImageMagick does not deal with apng. Let Video deal with it
137         }
138     }
139 
140     std::vector<Magick::Image> frames;
141     try {
142         readImages(&frames, filename());  // ideally, we could set max_frames
143     }
144     catch(Magick::Warning &warning) {
145         if (kDebug) fprintf(stderr, "Meh: %s (%s)\n",
146                             filename().c_str(), warning.what());
147     }
148     catch (std::exception& e) {
149         // No message, let that file be handled by the next handler.
150         if (kDebug) fprintf(stderr, "Exception: %s (%s)\n",
151                             filename().c_str(), e.what());
152         return false;
153     }
154 
155     if (frames.size() == 0) {
156         if (kDebug) fprintf(stderr, "No image found.");
157         return false;
158     }
159 
160     // We don't really know if something is an animation from the frames we
161     // got back (or is there ?), so we use a blacklist approach here: filenames
162     // that are known to be containers for multiple independent images are
163     // considered not an animation.
164     const bool could_be_animation =
165         !EndsWith(filename(), "ico") && !EndsWith(filename(), "pdf");
166 
167     // We can't remove the offset yet as the coalesceImages() might need images
168     // prior to our desired set.
169     if (frame_count > 0 && frame_offset + frame_count < (int)frames.size()) {
170         frames.resize(frame_offset + frame_count);
171     }
172 
173     std::vector<Magick::Image> result;
174     // Put together the animation from single frames. GIFs can have nasty
175     // disposal modes, but they are handled nicely by coalesceImages()
176     if (frames.size() > 1 && could_be_animation) {
177         Magick::coalesceImages(&result, frames.begin(), frames.end());
178         is_animation_ = true;
179     } else {
180         result.insert(result.end(), frames.begin(), frames.end());
181         is_animation_ = false;
182     }
183 
184     if (frame_offset > 0) {
185         frame_offset = std::min(frame_offset, (int)result.size() - 1);
186         result.erase(result.begin(), result.begin() + frame_offset);
187     }
188 
189     for (Magick::Image &img : result) {
190         ExifImageOp exif_op;
191         if (opts.exif_rotate) exif_op = GetExifOp(img);
192 
193         // We do trimming only if this is not an animation, which will likely
194         // not create a pleasent result.
195         if (!is_animation_) {
196             if (opts.crop_border > 0) {
197                 const int c = opts.crop_border;
198                 const int w = std::max(1, (int)img.columns() - 2*c);
199                 const int h = std::max(1, (int)img.rows() - 2*c);
200                 img.crop(Magick::Geometry(w, h, c, c));
201             }
202             if (opts.auto_crop) {
203                 img.trim();
204             }
205         }
206 
207         // Figure out scaling for the image.
208         int target_width = 0, target_height = 0;
209         if (CalcScaleToFitDisplay(img.columns(), img.rows(),
210                                   opts, abs(exif_op.angle) == 90,
211                                   &target_width, &target_height)) {
212             try {
213                 auto geometry = Magick::Geometry(target_width, target_height);
214                 geometry.aspect(true);  // Force to scale to given size.
215                 if (opts.antialias)
216                     img.scale(geometry);
217                 else
218                     img.sample(geometry);
219             }
220             catch (const std::exception& e) {
221                 if (kDebug) fprintf(stderr, "%s: %s\n",
222                                     filename().c_str(), e.what());
223                 return false;
224             }
225         }
226 
227         // Now that the image is nice and small, the following ops are cheap
228         if (exif_op.flip) img.flip();
229         img.rotate(exif_op.angle);
230 
231         frames_.push_back(new PreprocessedFrame(img, opts, result.size() > 1));
232     }
233 
234     max_frames_ = (frame_count < 0)
235         ? (int)frames_.size()
236         : std::min(frame_count, (int)frames_.size());
237 
238     return true;
239 }
240 
IndentationIfCentered(const PreprocessedFrame * frame) const241 int ImageLoader::IndentationIfCentered(const PreprocessedFrame *frame) const {
242     return options_.center_horizontally
243         ? (options_.width - frame->framebuffer().width()) / 2
244         : 0;
245 }
246 
SendFrames(Duration duration,int loops,const volatile sig_atomic_t & interrupt_received,const Renderer::WriteFramebufferFun & sink)247 void ImageLoader::SendFrames(Duration duration, int loops,
248                              const volatile sig_atomic_t &interrupt_received,
249                              const Renderer::WriteFramebufferFun &sink) {
250     if (options_.scroll_animation) {
251         Scroll(duration, loops, interrupt_received,
252                options_.scroll_dx, options_.scroll_dy, options_.scroll_delay,
253                sink);
254         return;
255     }
256 
257     int last_height = -1;  // First image emit will not have a height.
258     if (frames_.size() == 1 || !is_animation_)
259         loops = 1;   // If there is no animation, nothing to repeat.
260 
261     // Not initialized or negative value wants us to loop forever.
262     // (note, kNotInitialized is actually negative, but here for clarity
263     const bool loop_forever = (loops < 0) || (loops == timg::kNotInitialized);
264 
265     timg::Duration time_from_first_frame;
266     bool is_first = true;
267     for (int k = 0;
268          (loop_forever || k < loops)
269              && !interrupt_received
270              && time_from_first_frame < duration;
271          ++k) {
272         for (int f = 0; f < max_frames_ && !interrupt_received; ++f) {
273             const auto &frame = frames_[f];
274             time_from_first_frame.Add(frame->delay());
275             const int dx = IndentationIfCentered(frame);
276             const int dy = is_animation_ && last_height > 0 ? -last_height : 0;
277             SeqType seq_type = SeqType::FrameImmediate;
278             if (is_animation_) {
279                 seq_type = is_first
280                     ? SeqType::StartOfAnimation
281                     : SeqType::AnimationFrame;
282             }
283             sink(dx, dy, frame->framebuffer(), seq_type,
284                  std::min(time_from_first_frame, duration));
285             last_height = frame->framebuffer().height();
286             if (time_from_first_frame > duration) break;
287             is_first = false;
288         }
289     }
290 }
291 
gcd(int a,int b)292 static int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }
293 
Scroll(Duration duration,int loops,const volatile sig_atomic_t & interrupt_received,int dx,int dy,Duration scroll_delay,const Renderer::WriteFramebufferFun & write_fb)294 void ImageLoader::Scroll(Duration duration, int loops,
295                          const volatile sig_atomic_t &interrupt_received,
296                          int dx, int dy, Duration scroll_delay,
297                          const Renderer::WriteFramebufferFun &write_fb) {
298     if (frames_.size() > 1) {
299         if (kDebug) fprintf(stderr, "This is an %simage format, "
300                             "scrolling on top of that is not supported. "
301                             "Just doing the scrolling of the first frame.\n",
302                             is_animation_ ? "animated " : "multi-");
303         // TODO: do both.
304     }
305 
306     const Framebuffer &img = frames_[0]->framebuffer();
307     const int img_width = img.width();
308     const int img_height = img.height();
309 
310     const int display_w = std::min(options_.width, img_width);
311     const int display_h = std::min(options_.height, img_height);
312 
313     // Since the user can choose the number of cycles we go through it,
314     // we need to calculate what the minimum number of steps is we need
315     // to do the scroll. If this is just in one direction, that is simple: the
316     // number of pixel in that direction. If we go diagonal, then it is
317     // essentially the least common multiple of steps.
318     const int x_steps = (dx == 0)
319         ? 1
320         : ((img_width % abs(dx) == 0) ? img_width / abs(dx) : img_width);
321     const int y_steps = (dy == 0)
322         ? 1
323         : ((img_height % abs(dy) == 0) ? img_height / abs(dy) : img_height);
324     const int64_t cycle_steps =  x_steps * y_steps / gcd(x_steps, y_steps);
325 
326     // Depending if we go forward or backward, we want to start out aligned
327     // right or left.
328     // For negative direction, guarantee that we never run into negative numbers.
329     const int64_t x_init = (dx < 0)
330         ? (img_width - display_w - dx*cycle_steps) : 0;
331     const int64_t y_init = (dy < 0)
332         ? (img_height - display_h - dy*cycle_steps) : 0;
333     bool is_first = true;
334 
335     timg::Framebuffer display_fb(display_w, display_h);
336     timg::Duration time_from_first_frame;
337     for (int k = 0;
338          (loops < 0 || k < loops)
339              && !interrupt_received
340              && time_from_first_frame < duration;
341          ++k) {
342         for (int64_t cycle_pos = 0; cycle_pos <= cycle_steps; ++cycle_pos) {
343             if (interrupt_received || time_from_first_frame > duration)
344                 break;
345             const int64_t x_cycle_pos = dx*cycle_pos;
346             const int64_t y_cycle_pos = dy*cycle_pos;
347             for (int y = 0; y < display_h; ++y) {
348                 for (int x = 0; x < display_w; ++x) {
349                     const int x_src = (x_init + x_cycle_pos + x) % img_width;
350                     const int y_src = (y_init + y_cycle_pos + y) % img_height;
351                     display_fb.SetPixel(x, y, img.at(x_src, y_src));
352                 }
353             }
354             time_from_first_frame.Add(scroll_delay);
355             write_fb(0, is_first ? 0 : -display_fb.height(), display_fb,
356                      is_first
357                      ? SeqType::StartOfAnimation
358                      : SeqType::AnimationFrame,
359                      time_from_first_frame);
360             is_first = false;
361         }
362     }
363 }
364 
365 }  // namespace timg
366