1 // Copyright 2008-present Contributors to the OpenImageIO project.
2 // SPDX-License-Identifier: BSD-3-Clause
3 // https://github.com/OpenImageIO/oiio/blob/master/LICENSE.md
4 
5 #include "sgi_pvt.h"
6 
7 
8 OIIO_PLUGIN_NAMESPACE_BEGIN
9 
10 // Obligatory material to make this a recognizeable imageio plugin
11 OIIO_PLUGIN_EXPORTS_BEGIN
12 OIIO_EXPORT ImageOutput*
sgi_output_imageio_create()13 sgi_output_imageio_create()
14 {
15     return new SgiOutput;
16 }
17 OIIO_EXPORT const char* sgi_output_extensions[] = { "sgi", "rgb",  "rgba", "bw",
18                                                     "int", "inta", nullptr };
19 OIIO_PLUGIN_EXPORTS_END
20 
21 
22 
23 int
supports(string_view feature) const24 SgiOutput::supports(string_view feature) const
25 {
26     return (feature == "alpha" || feature == "nchannels");
27 }
28 
29 
30 
31 bool
open(const std::string & name,const ImageSpec & spec,OpenMode mode)32 SgiOutput::open(const std::string& name, const ImageSpec& spec, OpenMode mode)
33 {
34     if (mode != Create) {
35         errorf("%s does not support subimages or MIP levels", format_name());
36         return false;
37     }
38 
39     close();  // Close any already-opened file
40     // saving 'name' and 'spec' for later use
41     m_filename = name;
42     m_spec     = spec;
43 
44     if (m_spec.width >= 65535 || m_spec.height >= 65535) {
45         errorf("Exceeds the maximum resolution (65535)");
46         return false;
47     }
48 
49     m_fd = Filesystem::fopen(m_filename, "wb");
50     if (!m_fd) {
51         errorf("Could not open \"%s\"", name);
52         return false;
53     }
54 
55     // SGI image files only supports UINT8 and UINT16.  If something
56     // else was requested, revert to the one most likely to be readable
57     // by any SGI reader: UINT8
58     if (m_spec.format != TypeDesc::UINT8 && m_spec.format != TypeDesc::UINT16)
59         m_spec.set_format(TypeDesc::UINT8);
60     m_dither = (m_spec.format == TypeDesc::UINT8)
61                    ? m_spec.get_int_attribute("oiio:dither", 0)
62                    : 0;
63 
64     // If user asked for tiles -- which this format doesn't support, emulate
65     // it by buffering the whole image.
66     if (m_spec.tile_width && m_spec.tile_height)
67         m_tilebuffer.resize(m_spec.image_bytes());
68 
69     return create_and_write_header();
70 }
71 
72 
73 
74 bool
write_scanline(int y,int z,TypeDesc format,const void * data,stride_t xstride)75 SgiOutput::write_scanline(int y, int z, TypeDesc format, const void* data,
76                           stride_t xstride)
77 {
78     y    = m_spec.height - y - 1;
79     data = to_native_scanline(format, data, xstride, m_scratch, m_dither, y, z);
80 
81     // In SGI format all channels are saved to file separately: firsty all
82     // channel 1 scanlines are saved, then all channel2 scanlines are saved
83     // and so on.
84     //
85     // Note that since SGI images are pretty archaic and most probably
86     // people won't be too picky about full flexibility writing them, we
87     // content ourselves with only writing uncompressed data, and don't
88     // attempt to write with RLE encoding.
89 
90     size_t bpc = m_spec.format.size();  // bytes per channel
91     std::unique_ptr<unsigned char[]> channeldata(
92         new unsigned char[m_spec.width * bpc]);
93 
94     for (int64_t c = 0; c < m_spec.nchannels; ++c) {
95         unsigned char* cdata = (unsigned char*)data + c * bpc;
96         for (int64_t x = 0; x < m_spec.width; ++x) {
97             channeldata[x * bpc] = cdata[0];
98             if (bpc == 2)
99                 channeldata[x * bpc + 1] = cdata[1];
100             cdata += m_spec.nchannels * bpc;  // advance to next pixel
101         }
102         if (bpc == 2 && littleendian())
103             swap_endian((unsigned short*)&channeldata[0], m_spec.width);
104         ptrdiff_t scanline_offset = sgi_pvt::SGI_HEADER_LEN
105                                     + ptrdiff_t(c * m_spec.height + y)
106                                           * m_spec.width * bpc;
107         Filesystem::fseek(m_fd, scanline_offset, SEEK_SET);
108         if (!fwrite(&channeldata[0], 1, m_spec.width * bpc)) {
109             return false;
110         }
111     }
112 
113     return true;
114 }
115 
116 
117 
118 bool
write_tile(int x,int y,int z,TypeDesc format,const void * data,stride_t xstride,stride_t ystride,stride_t zstride)119 SgiOutput::write_tile(int x, int y, int z, TypeDesc format, const void* data,
120                       stride_t xstride, stride_t ystride, stride_t zstride)
121 {
122     // Emulate tiles by buffering the whole image
123     return copy_tile_to_image_buffer(x, y, z, format, data, xstride, ystride,
124                                      zstride, &m_tilebuffer[0]);
125 }
126 
127 
128 
129 bool
close()130 SgiOutput::close()
131 {
132     if (!m_fd) {  // already closed
133         init();
134         return true;
135     }
136 
137     bool ok = true;
138     if (m_spec.tile_width) {
139         // Handle tile emulation -- output the buffered pixels
140         OIIO_ASSERT(m_tilebuffer.size());
141         ok &= write_scanlines(m_spec.y, m_spec.y + m_spec.height, 0,
142                               m_spec.format, &m_tilebuffer[0]);
143         std::vector<unsigned char>().swap(m_tilebuffer);
144     }
145 
146     fclose(m_fd);
147     init();
148     return ok;
149 }
150 
151 
152 
153 bool
create_and_write_header()154 SgiOutput::create_and_write_header()
155 {
156     sgi_pvt::SgiHeader sgi_header;
157     sgi_header.magic   = sgi_pvt::SGI_MAGIC;
158     sgi_header.storage = sgi_pvt::VERBATIM;
159     sgi_header.bpc     = m_spec.format.size();
160 
161     if (m_spec.height == 1 && m_spec.nchannels == 1)
162         sgi_header.dimension = sgi_pvt::ONE_SCANLINE_ONE_CHANNEL;
163     else if (m_spec.nchannels == 1)
164         sgi_header.dimension = sgi_pvt::MULTI_SCANLINE_ONE_CHANNEL;
165     else
166         sgi_header.dimension = sgi_pvt::MULTI_SCANLINE_MULTI_CHANNEL;
167 
168     sgi_header.xsize  = m_spec.width;
169     sgi_header.ysize  = m_spec.height;
170     sgi_header.zsize  = m_spec.nchannels;
171     sgi_header.pixmin = 0;
172     sgi_header.pixmax = (sgi_header.bpc == 1) ? 255 : 65535;
173     sgi_header.dummy  = 0;
174 
175     ParamValue* ip = m_spec.find_attribute("ImageDescription",
176                                            TypeDesc::STRING);
177     if (ip && ip->data()) {
178         const char** img_descr = (const char**)ip->data();
179         strncpy(sgi_header.imagename, *img_descr, 80);
180         sgi_header.imagename[79] = 0;
181     }
182 
183     sgi_header.colormap = sgi_pvt::NORMAL;
184 
185     if (littleendian()) {
186         swap_endian(&sgi_header.magic);
187         swap_endian(&sgi_header.dimension);
188         swap_endian(&sgi_header.xsize);
189         swap_endian(&sgi_header.ysize);
190         swap_endian(&sgi_header.zsize);
191         swap_endian(&sgi_header.pixmin);
192         swap_endian(&sgi_header.pixmax);
193         swap_endian(&sgi_header.colormap);
194     }
195 
196     char dummy[404] = { 0 };
197     if (!fwrite(&sgi_header.magic) || !fwrite(&sgi_header.storage)
198         || !fwrite(&sgi_header.bpc) || !fwrite(&sgi_header.dimension)
199         || !fwrite(&sgi_header.xsize) || !fwrite(&sgi_header.ysize)
200         || !fwrite(&sgi_header.zsize) || !fwrite(&sgi_header.pixmin)
201         || !fwrite(&sgi_header.pixmax) || !fwrite(&sgi_header.dummy)
202         || !fwrite(sgi_header.imagename, 1, 80) || !fwrite(&sgi_header.colormap)
203         || !fwrite(dummy, 404, 1)) {
204         errorf("Error writing to \"%s\"", m_filename);
205         return false;
206     }
207     return true;
208 }
209 
210 OIIO_PLUGIN_NAMESPACE_END
211