Partial conversion of PlayerSubtitles -> PlayerText and SubtitleString -> PlainText.
[dcpomatic.git] / src / lib / magick_image_proxy.cc
1 /*
2     Copyright (C) 2014-2015 Carl Hetherington <cth@carlh.net>
3
4     This file is part of DCP-o-matic.
5
6     DCP-o-matic is free software; you can redistribute it and/or modify
7     it under the terms of the GNU General Public License as published by
8     the Free Software Foundation; either version 2 of the License, or
9     (at your option) any later version.
10
11     DCP-o-matic is distributed in the hope that it will be useful,
12     but WITHOUT ANY WARRANTY; without even the implied warranty of
13     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14     GNU General Public License for more details.
15
16     You should have received a copy of the GNU General Public License
17     along with DCP-o-matic.  If not, see <http://www.gnu.org/licenses/>.
18
19 */
20
21 #include "magick_image_proxy.h"
22 #include "cross.h"
23 #include "exceptions.h"
24 #include "dcpomatic_socket.h"
25 #include "image.h"
26 #include "compose.hpp"
27 #include <Magick++.h>
28 #include <libxml++/libxml++.h>
29 #include <iostream>
30
31 #include "i18n.h"
32
33 using std::string;
34 using std::cout;
35 using std::pair;
36 using std::make_pair;
37 using boost::shared_ptr;
38 using boost::optional;
39 using boost::dynamic_pointer_cast;
40
41 MagickImageProxy::MagickImageProxy (boost::filesystem::path path)
42 {
43         /* Read the file into a Blob */
44
45         boost::uintmax_t const size = boost::filesystem::file_size (path);
46         FILE* f = fopen_boost (path, "rb");
47         if (!f) {
48                 throw OpenFileError (path, errno, true);
49         }
50
51         uint8_t* data = new uint8_t[size];
52         if (fread (data, 1, size, f) != size) {
53                 delete[] data;
54                 throw ReadFileError (path);
55         }
56
57         fclose (f);
58         _blob.update (data, size);
59         delete[] data;
60 }
61
62 MagickImageProxy::MagickImageProxy (shared_ptr<cxml::Node>, shared_ptr<Socket> socket)
63 {
64         uint32_t const size = socket->read_uint32 ();
65         uint8_t* data = new uint8_t[size];
66         socket->read (data, size);
67         _blob.update (data, size);
68         delete[] data;
69 }
70
71 pair<shared_ptr<Image>, int>
72 MagickImageProxy::image (optional<dcp::NoteHandler>, optional<dcp::Size>) const
73 {
74         boost::mutex::scoped_lock lm (_mutex);
75
76         if (_image) {
77                 return make_pair (_image, 0);
78         }
79
80         Magick::Image* magick_image = 0;
81         string error;
82         try {
83                 magick_image = new Magick::Image (_blob);
84         } catch (Magick::Exception& e) {
85                 error = e.what ();
86         }
87
88         if (!magick_image) {
89                 /* ImageMagick cannot auto-detect Targa files, it seems, so try here with an
90                    explicit format.  I can't find it documented that passing a (0, 0) geometry
91                    is allowed, but it seems to work.
92                 */
93                 try {
94                         magick_image = new Magick::Image (_blob, Magick::Geometry (0, 0), "TGA");
95                 } catch (...) {
96
97                 }
98         }
99
100         if (!magick_image) {
101                 /* If we failed both an auto-detect and a forced-Targa we give the error from
102                    the auto-detect.
103                 */
104                 throw DecodeError (String::compose (_("Could not decode image file (%1)"), error));
105         }
106
107         unsigned char const * data = static_cast<unsigned char const *>(_blob.data());
108         if (data[801] == 1 || magick_image->image()->colorspace == Magick::sRGBColorspace) {
109                 /* Either:
110                    1.  The transfer characteristic in this file is "printing density"; in this case ImageMagick sets the colour space
111                        to LogColorspace, or
112                    2.  The file is sRGB.
113
114                    Empirically we find that in these cases if we subsequently call colorSpace(Magick::RGBColorspace) the colours
115                    are very wrong.  To prevent this, set the image colour space to RGB to stop the ::colorSpace call below doing
116                    anything.  See #1123 and others.
117                 */
118                 magick_image->image()->colorspace = Magick::RGBColorspace;
119         }
120
121         magick_image->colorSpace(Magick::RGBColorspace);
122
123         dcp::Size size (magick_image->columns(), magick_image->rows());
124
125         _image.reset (new Image (AV_PIX_FMT_RGB24, size, true));
126
127         /* Write line-by-line here as _image must be aligned, and write() cannot be told about strides */
128         uint8_t* p = _image->data()[0];
129         for (int i = 0; i < size.height; ++i) {
130 #ifdef DCPOMATIC_HAVE_MAGICKCORE_NAMESPACE
131                 using namespace MagickCore;
132 #endif
133 #ifdef DCPOMATIC_HAVE_MAGICKLIB_NAMESPACE
134                 using namespace MagickLib;
135 #endif
136                 magick_image->write (0, i, size.width, 1, "RGB", CharPixel, p);
137                 p += _image->stride()[0];
138         }
139
140         delete magick_image;
141
142         return make_pair (_image, 0);
143 }
144
145 void
146 MagickImageProxy::add_metadata (xmlpp::Node* node) const
147 {
148         node->add_child("Type")->add_child_text (N_("Magick"));
149 }
150
151 void
152 MagickImageProxy::send_binary (shared_ptr<Socket> socket) const
153 {
154         socket->write (_blob.length ());
155         socket->write ((uint8_t *) _blob.data (), _blob.length ());
156 }
157
158 bool
159 MagickImageProxy::same (shared_ptr<const ImageProxy> other) const
160 {
161         shared_ptr<const MagickImageProxy> mp = dynamic_pointer_cast<const MagickImageProxy> (other);
162         if (!mp) {
163                 return false;
164         }
165
166         if (_blob.length() != mp->_blob.length()) {
167                 return false;
168         }
169
170         return memcmp (_blob.data(), mp->_blob.data(), _blob.length()) == 0;
171 }
172
173 AVPixelFormat
174 MagickImageProxy::pixel_format () const
175 {
176         return AV_PIX_FMT_RGB24;
177 }
178
179 size_t
180 MagickImageProxy::memory_used () const
181 {
182         size_t m = _blob.length();
183         if (_image) {
184                 m += _image->memory_used();
185         }
186         return m;
187 }