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