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 #pragma once
6 
7 // Format reference: ftp://ftp.sgi.com/graphics/SGIIMAGESPEC
8 
9 #include <cstdio>
10 
11 #include <OpenImageIO/filesystem.h>
12 #include <OpenImageIO/fmath.h>
13 #include <OpenImageIO/imageio.h>
14 
15 OIIO_PLUGIN_NAMESPACE_BEGIN
16 
17 namespace sgi_pvt {
18 
19 // magic number identifying SGI file
20 const short SGI_MAGIC = 0x01DA;
21 
22 // SGI file header - all fields are written in big-endian to file
23 struct SgiHeader {
24     int16_t magic;       // must be 0xDA01 (big-endian)
25     int8_t storage;      // compression used, see StorageFormat enum
26     int8_t bpc;          // number of bytes per pixel channel
27     uint16_t dimension;  // dimension of he image, see Dimension
28     uint16_t xsize;      // width in pixels
29     uint16_t ysize;      // height in pixels
30     uint16_t zsize;      // number of channels: 1(B/W), 3(RGB) or 4(RGBA)
31     int32_t pixmin;      // minimum pixel value
32     int32_t pixmax;      // maximum pixel value
33     int32_t dummy;       // unused, should be set to 0
34     char imagename[80];  // null terminated ASCII string
35     int32_t colormap;    // how pixels should be interpreted
36                          // see ColorMap enum
37 };
38 
39 // size of the header with all dummy bytes
40 const int SGI_HEADER_LEN = 512;
41 
42 enum StorageFormat {
43     VERBATIM = 0,  // uncompressed
44     RLE            // RLE compressed
45 };
46 
47 enum Dimension {
48     ONE_SCANLINE_ONE_CHANNEL = 1,  // single scanline and single channel
49     MULTI_SCANLINE_ONE_CHANNEL,    // multiscanline, single channel
50     MULTI_SCANLINE_MULTI_CHANNEL   // multiscanline, multichannel
51 };
52 
53 enum ColorMap {
54     NORMAL = 0,  // B/W image for 1 channel, RGB for 3 channels and RGBA for 4
55     DITHERED,  // only one channel of data, RGB values are packed int one byte:
56                // red and green - 3 bits, blue - 2 bits; obsolete
57     SCREEN,    // obsolete
58     COLORMAP   // TODO: what is this?
59 };
60 
61 }  // namespace sgi_pvt
62 
63 
64 
65 class SgiInput final : public ImageInput {
66 public:
SgiInput()67     SgiInput() { init(); }
~SgiInput()68     virtual ~SgiInput() { close(); }
format_name(void)69     virtual const char* format_name(void) const override { return "sgi"; }
70     virtual bool valid_file(const std::string& filename) const override;
71     virtual bool open(const std::string& name, ImageSpec& spec) override;
72     virtual bool close(void) override;
73     virtual bool read_native_scanline(int subimage, int miplevel, int y, int z,
74                                       void* data) override;
75 
76 private:
77     FILE* m_fd = nullptr;
78     std::string m_filename;
79     sgi_pvt::SgiHeader m_sgi_header;
80     std::vector<uint32_t> start_tab;
81     std::vector<uint32_t> length_tab;
82 
init()83     void init()
84     {
85         m_fd = nullptr;
86         memset(&m_sgi_header, 0, sizeof(m_sgi_header));
87     }
88 
89     // reads SGI file header (512 bytes) into m_sgi_header
90     // Return true if ok, false if there was a read error.
91     bool read_header();
92 
93     // reads RLE scanline start offset and RLE scanline length tables
94     // RLE scanline start offset is stored in start_tab
95     // RLE scanline length is stored in length_tab
96     // Return true if ok, false if there was a read error.
97     bool read_offset_tables();
98 
99     // read channel scanline data from file, uncompress it and save the data to
100     // 'out' buffer; 'out' should be allocate before call to this method.
101     // Return true if ok, false if there was a read error.
102     bool uncompress_rle_channel(int scanline_off, int scanline_len,
103                                 unsigned char* out);
104 
105     /// Helper: read, with error detection
106     ///
fread(void * buf,size_t itemsize,size_t nitems)107     bool fread(void* buf, size_t itemsize, size_t nitems)
108     {
109         size_t n = ::fread(buf, itemsize, nitems, m_fd);
110         if (n != nitems)
111             errorf("Read error");
112         return n == nitems;
113     }
114 };
115 
116 
117 
118 class SgiOutput final : public ImageOutput {
119 public:
SgiOutput()120     SgiOutput() {}
~SgiOutput()121     virtual ~SgiOutput() { close(); }
format_name(void)122     virtual const char* format_name(void) const override { return "sgi"; }
123     virtual int supports(string_view feature) const override;
124     virtual bool open(const std::string& name, const ImageSpec& spec,
125                       OpenMode mode = Create) override;
126     virtual bool close(void) override;
127     virtual bool write_scanline(int y, int z, TypeDesc format, const void* data,
128                                 stride_t xstride) override;
129     virtual bool write_tile(int x, int y, int z, TypeDesc format,
130                             const void* data, stride_t xstride,
131                             stride_t ystride, stride_t zstride) override;
132 
133 private:
134     FILE* m_fd = nullptr;
135     std::string m_filename;
136     std::vector<unsigned char> m_scratch;
137     unsigned int m_dither;
138     std::vector<unsigned char> m_tilebuffer;
139 
init()140     void init() { m_fd = NULL; }
141 
142     bool create_and_write_header();
143 
144     /// Helper - write, with error detection
145     template<class T>
146     bool fwrite(const T* buf, size_t itemsize = sizeof(T), size_t nitems = 1)
147     {
148         size_t n = std::fwrite(buf, itemsize, nitems, m_fd);
149         if (n != nitems)
150             errorf("Error writing \"%s\" (wrote %d/%d records)", m_filename,
151                    (int)n, (int)nitems);
152         return n == nitems;
153     }
154 };
155 
156 
157 OIIO_PLUGIN_NAMESPACE_END
158