image: add crn support to the image plugin.
[xonotic/netradiant.git] / libs / crunch / crn_rgba.cpp
1 /*
2    Copyright (C) 2018, Unvanquished Developers
3    All Rights Reserved.
4
5    This file is part of NetRadiant.
6
7    NetRadiant is free software; you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation; either version 2 of the License, or
10    (at your option) any later version.
11
12    NetRadiant is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with NetRadiant; if not, write to the Free Software
19    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
20  */
21
22
23 #include "crn_rgba.h"
24
25 #include <string.h>
26
27 #include <memory>
28
29
30 #include "ddslib.h"
31 #include "crn_decomp.h"
32
33 int LittleLong(int l) {
34 #if GDEF_ARCH_ENDIAN_BIG
35     std::reverse(reinterpret_cast<unsigned char *>( &l ), reinterpret_cast<unsigned char *>( &l ) + sizeof(int));
36 #endif
37     return l;
38 }
39
40 // Sets `x` and `y` to the width and height of the input crn image. Returns false if there is an
41 // error reading the image.
42 extern "C" int GetCRNImageSize(const void *buffer, int length, int *x, int *y) {
43     crnd::crn_texture_info ti;
44     if(!crnd::crnd_get_texture_info(buffer, length, &ti) ||
45       // Ensure we are not trying to load a cubemap (which has 6 faces...)
46       (ti.m_faces != 1) ) {
47         return false;
48     }
49     if (x) *x = ti.m_width;
50     if (y) *y = ti.m_height;
51     return true;
52 }
53
54 // Converts a .crn file to RGBA. Stores the pixels in outBuf. Use GetCRNImageSize to get the image
55 // size to determine how big outBuf should be. The function will return false if the image does not
56 // fit inside outBuf.
57 extern "C" int ConvertCRNtoRGBA(const void *buffer, int length, int outBufLen, void* outBuf) {
58     crnd::crn_texture_info ti;
59     if(!crnd::crnd_get_texture_info(buffer, length, &ti) ||
60       // Ensure we are not trying to load a cubemap (which has 6 faces...)
61       (ti.m_faces != 1) ) {
62         return false;
63     }
64
65     // Sanity check mipmaps.
66     if (ti.m_levels <= 0) {
67         return false;
68     }
69
70     // The largest layer is always layer 0, so load that one.
71     crnd::crn_level_info li;
72     if (!crnd::crnd_get_level_info( buffer, length, 0, &li)) {
73         return false;
74     }
75
76     // Ensure we can fit the final image in outBuf.
77     if (outBufLen < ti.m_width * ti.m_height) {
78         return false;
79     }
80
81     crnd::crnd_unpack_context ctx = crnd::crnd_unpack_begin(buffer, length);
82     if (!ctx) {
83         return false;
84     }
85
86     // Since the texture is compressed and the crunch library doesn't provide the code to convert the code
87     // to RGBAImage, we'll need to convert it to DDS first and use the DDS decompression routines to get
88     // the raw pixels (theoretically, we could refactor the DDS functions to be generalized, but for now,
89     // this seems much more maintainable...). This code is cribbed from the example code in
90     // the crunch repo: https://github.com/DaemonEngine/crunch/blob/master/example2/example2.cpp
91     // Compute the face's width, height, number of DXT blocks per row/col, etc.
92     // This is not a proper DDS conversion; it's only enough to get the ddslib decompressor to be happy.
93     const crn_uint32 blocks_x = std::max(1U, (ti.m_width + 3) >> 2);
94     const crn_uint32 blocks_y = std::max(1U, (ti.m_height + 3) >> 2);
95     const crn_uint32 row_pitch = blocks_x * crnd::crnd_get_bytes_per_dxt_block(ti.m_format);
96     const crn_uint32 total_face_size = row_pitch * blocks_y;
97     const crn_uint32 ddsSize = sizeof(ddsBuffer_t) + total_face_size;
98     std::unique_ptr<char> ddsBuffer(new char[ddsSize]);
99     memset(ddsBuffer.get(), 0, ddsSize);
100
101
102     ddsBuffer_t* dds = reinterpret_cast<ddsBuffer_t*>(ddsBuffer.get());
103
104     memcpy(&dds->magic, "DDS ", sizeof(dds->magic));
105     dds->size = LittleLong(124);  // Size of the DDS header.
106     dds->height = LittleLong(ti.m_height);
107     dds->width = LittleLong(ti.m_width);
108     dds->mipMapCount = LittleLong(1);
109
110     dds->pixelFormat.size = LittleLong(sizeof(ddsPixelFormat_t));
111
112     crn_format fundamental_fmt = crnd::crnd_get_fundamental_dxt_format(ti.m_format);
113     dds->pixelFormat.fourCC = LittleLong(crnd::crnd_crn_format_to_fourcc(fundamental_fmt));
114     if (fundamental_fmt != ti.m_format) {
115         // It's a funky swizzled DXTn format - write its FOURCC to RGBBitCount.
116         dds->pixelFormat.rgbBitCount = LittleLong(crnd::crnd_crn_format_to_fourcc(ti.m_format));
117     }
118     char* imageArray[1];
119     imageArray[0] = reinterpret_cast<char*>(&dds->data);
120     if (!crnd::crnd_unpack_level(ctx, reinterpret_cast<void**>(&imageArray), total_face_size, row_pitch, 0)) {
121         return false;
122     }
123
124     if (DDSDecompress(dds, reinterpret_cast<unsigned char*>(outBuf)) == -1) {
125         return false;
126     }
127     return true;
128 }