├── .gitignore
├── LICENSE
├── NormalTextureProcessor.cpp
├── NormalTextureProcessor.sln
├── NormalTextureProcessor.vcxproj
├── README.md
├── image_iterator.cpp
├── lodepng.cpp
├── lodepng.h
├── readme_clearcoat_diff.png
├── readme_heatmap.png
├── readme_lamp_render_diff.png
├── readme_sample.png
├── readme_wicker_render_diff.png
├── readtga.cpp
├── readtga.h
├── rwpng.cpp
├── rwpng.h
├── stdafx.h
├── stdio.cpp
├── targetver.h
├── test_files
├── Heightfields
│ ├── grayscale_wood_floor.png
│ ├── ntp_heightfield.png
│ ├── r_bump_map.png
│ └── squiggles.png
├── LICENSE
├── NoneOfTheAbove
│ ├── black.tga
│ ├── cyan.png
│ ├── fade.png
│ ├── gray.png
│ ├── white.tga
│ ├── wood_floor.png
│ └── yellow.png
├── README.md
├── Standard
│ ├── lava_flow_n.png
│ ├── ntp_heightfield_gimp.png
│ ├── ntp_heightfield_gimp_5scale.png
│ ├── ntp_heightfield_normalmap_online.png
│ ├── ntp_resize_128.png
│ ├── ntp_resize_512.png
│ ├── r_normal_map.png
│ ├── r_normal_map_reversed_y.png
│ ├── squiggles_gimp.png
│ ├── squiggles_normalmap_online_zneg.png
│ ├── squiggles_ntp_quick.png
│ ├── squiggles_ntp_roundtrip.png
│ └── targa_r_normal_map.tga
├── XYtextures
│ └── acacia_door_bottom_n.png
├── ZZero
│ ├── r_normal_map_reversed_x_0_bias_z.png
│ └── squiggles_normalmap_online_zzero.png
└── run_test_suite.bat
├── tga.h
└── tga_decoder.cpp
/.gitignore:
--------------------------------------------------------------------------------
1 | x64
2 | .vs
3 | NormalTextureProcessor.vcxproj.filters
4 | NormalTextureProcessor.vcxproj.user
5 | test_files/_output_*
6 | test_files/*.log
7 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Eric Haines
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/NormalTextureProcessor.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.8.34511.84
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "NormalTextureProcessor", "NormalTextureProcessor.vcxproj", "{D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x64 = Debug|x64
11 | Debug|x86 = Debug|x86
12 | Release|x64 = Release|x64
13 | Release|x86 = Release|x86
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Debug|x64.ActiveCfg = Debug|x64
17 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Debug|x64.Build.0 = Debug|x64
18 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Debug|x86.ActiveCfg = Debug|Win32
19 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Debug|x86.Build.0 = Debug|Win32
20 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Release|x64.ActiveCfg = Release|x64
21 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Release|x64.Build.0 = Release|x64
22 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Release|x86.ActiveCfg = Release|Win32
23 | {D8E8388B-6047-4D9D-BA41-6C6ECC5FC3E4}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {2CEEC209-F87E-4C0C-9AFF-60F3C436F6EF}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/NormalTextureProcessor.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | 17.0
23 | Win32Proj
24 | {d8e8388b-6047-4d9d-ba41-6c6ecc5fc3e4}
25 | NormalTextureProcessor
26 | 10.0
27 |
28 |
29 |
30 | Application
31 | true
32 | v143
33 | Unicode
34 |
35 |
36 | Application
37 | false
38 | v143
39 | true
40 | Unicode
41 |
42 |
43 | Application
44 | true
45 | v143
46 | Unicode
47 |
48 |
49 | Application
50 | false
51 | v143
52 | true
53 | Unicode
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 | Level3
76 | true
77 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
78 | true
79 |
80 |
81 | Console
82 | true
83 |
84 |
85 |
86 |
87 | Level3
88 | true
89 | true
90 | true
91 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
92 | true
93 |
94 |
95 | Console
96 | true
97 | true
98 | true
99 |
100 |
101 |
102 |
103 | true
104 | _DEBUG;_CONSOLE;%(PreprocessorDefinitions)
105 | true
106 | Level3
107 |
108 |
109 | Console
110 | true
111 |
112 |
113 |
114 |
115 | Level4
116 | true
117 | true
118 | true
119 | NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
120 | true
121 |
122 |
123 | Console
124 | true
125 | true
126 | true
127 |
128 |
129 |
130 |
131 |
132 |
133 | Level4
134 | true
135 | true
136 |
137 |
138 |
139 | Level4
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # NormalTextureProcessor (aka NTP)
2 |
3 | ## Description
4 |
5 | This C++ project examines normals textures (i.e., textures for applying bumps to surfaces), checking them for validity, and cleaning them up or converting them as desired.
6 |
7 | 
8 |
9 | A considerable percentage of normals textures out there have badly computed Z (and sometimes X and Y) values. In practice you could always derive Z from X and Y in your shaders. However, that can have a (usually small) performance hit. Also, files for glTF and USD files, among others, expect that the XYZ values are valid. Give the model to someone else and their viewer may not fix the Z values and then give a different result. So, if you want to store XYZ triplets with proper Z values, use this program to analyze and clean up your normals textures.
10 |
11 | ### How Normals Go Bad
12 |
13 | First and foremost, it's hard for us humans to see when a normals textures _is_ bad. There's a certain color to the textures, but unless they're wildly wrong, it's not easy to notice that the Z values are incorrect. Because they're hard to detect, normals can go bad and not be noticed. That's why I wrote this program, because I kept running into incorrect normals that the creators didn't realize were wrong.
14 |
15 | There are plenty of ways that the XYZ values for a normals texture can go bad. The tool producing the normals texture, typically converting from a grayscale heightfield, can be faulty. As much as I like [Normalmap Online](https://cpetry.github.io/NormalMap-Online/), [github](https://github.com/cpetry/NormalMap-Online), I know it produces imprecise normals textures. I hope to examine and evaluate other programs as I go, which is why I provide copious algorithm notes below - there's a right way to convert from XYZ to RGB. I have also seen evidence that along the fringes of cutout objects the normals will be off; I'm not sure why.
16 |
17 | Another source of error occurs when a normals texture is resized. If you blithely change the dimensions of a texture, the new normals texture will look reasonable and probably work fairly well. But, it will likely be wrong, a little bit or a lot. Blending two or more normalized normals together will rarely give a normalized result. After resizing, the Z values stored should be rederived. X and Y values may also stray a bit, creating vectors that are illegal. Whatever the case, this program can clean up such data. That said, a more accurate way to resize would be to work from the original heightfield image and convert to a normals texture after resizing.
18 |
19 | Finally, I've heard - see [tweet replies](https://twitter.com/pointinpolygon/status/1768730543234117924) - that artists sometimes modify the separate RGB channels for effect.
20 |
21 | ## Installation
22 |
23 | Develop on Windows 10 on Visual Studio 2022 (double-click NormalTextureProcessor.sln, build Release version). You might be able to compile and run it elsewhere, as the program is pretty Windows-free and is purely command-line driven, with no graphical user interface.
24 |
25 | The executable is available for download, [zip file here](https://www.realtimerendering.com/erich/download/NormalTextureProcessor.zip). Last updated March 20, 2024.
26 |
27 | ### Test Suite
28 |
29 | Optionally, after building the Release version, you should be able to go into the **test_files** directory and double-click on **run_test_suite.bat**. As the [README in that directory](https://github.com/erich666/NormalTextureProcessor/tree/main/test_files) notes, running this test file will create various separate output directories where the results are put. You can look at **run_test_suite.bat** to see various options in use and look at the resulting files.
30 |
31 | ## Usage
32 |
33 | **Quick start:** to analyze textures in a particular directory, run the exe like so:
34 | ```sh
35 | NormalTextureProcessor.exe -a -v -idir path_to_your_input_directory
36 | ```
37 | This will give an analysis of the textures in that directory. At the end of the analysis will be a summary like this:
38 | File types found, by category:
39 | ```sh
40 | Standard normals textures: chessboard_normal.png Knight_normal.png
41 | Standard normals textures that are not perfectly normalized: Knight_normal.png
42 | XY-only normals textures: bishop_black_normal.png bishop_white_normal.png Castle_normal.png
43 | ```
44 | "XY-only normals textures" are ones where the analyzer found that some of the Z values stored are wrong, they don't form normalized normals. This can sometimes be on purpose, but if you're expecting RGB to convert to XYZ, then those textures are poorly formed and should be cleaned up (see more about that below). The "Standard normals textures" are basically fine. Those list as not perfectly normalized are just that: the Z's are close, but could be better. Cleaning these, too, is a fine thing to do, though usually not as critical.
45 |
46 | Currently the system is limited to reading in 8-bit PNG and TGA (Targa) textures. 16-bit PNG images can be read in, but are currently converted to 8 bits. To avoid name collisions with PNGs, Targa files read in and manipulated are output with the suffix "_TGA" added to the input file name, output as PNGs. JPEG is not supported, and I warn against using it unless forced, as its lossy compression is almost guaranteed to generate RGB's that convert to XYZ's that are not normalized.
47 |
48 | A fussy note on terminology: I use the term "normals texture" for any (usually blue-ish) texture that stores normal directions as RGB (or just RG) values. "Normal texture" can be confusing - what's an "abnormal texture"? I avoid the word "map" and always use "texture," but you'll see "normal map" commonly used instead of "normal texture" elsewhere. Technically, the "map" is how the texture is applied to a surface, a separate function irrelevant here. Various analysis messages in the program talk about "one level" or "two levels", which refers to 8-bit values. For example, if a blue channel value was expected to be 228 but was 230, that is two levels of difference.
49 |
50 | ### Analysis
51 |
52 | To list out all the command-line options, simply don't put any:
53 | ```sh
54 | NormalTextureProcessor.exe
55 | ```
56 |
57 | This list of options will be confusing, so here are some typical combinations. To analyze all PNG and TGA textures in the current directory:
58 | ```sh
59 | NormalTextureProcessor.exe -a -v
60 | ```
61 | The '-v' is optional. It means "verbose", giving you additional information during processing, mostly about what files were cleaned. I recommend trying it on and off to see what you're missing.
62 |
63 | A report is generated when '-a' is selected. The lists at the end of the analysis can be used to copy and paste specific files into the program itself, e.g.:
64 | ```sh
65 | NormalTextureProcessor.exe -izneg -a bishop_black_normal.png bishop_white_normal.png Castle_normal.png
66 | ```
67 | Adding '-izneg' says to assume the incoming textures are "standard" -1 to 1 Z-value textures and analyze them as such. This setting is recommended for checking compliance with the glTF and USD standards, for example. By putting specific files on the command line, the program processes only them. This example doesn't do much, just analyzes the (already analyzed) set of files again. The main use is that you can specify individual files for other operations, such as cleanup and conversion.
68 |
69 | You can also feed in an input directory located elsewhere:
70 | ```sh
71 | NormalTextureProcessor.exe -a -idir flora/materials/normal_textures > analysis.log 2>&1
72 | ```
73 | The "> analysis.log 2>&1" is optional, it's a way to put all the analysis and any warnings or errors into a file.
74 |
75 | There are three main types of normals textures this program recognizes. They are:
76 | * RGB "standard" normals textures: each color channel is interpreted as representing numbers that go from -1.0 to 1.0, for all channels.
77 | * RGB normal, "Z-zero": as above, but the blue, Z, channel is instead interpreted as going from 0.0 to 1.0.
78 | * heightfield texture: an elevation texture, usually a grayscale image, typically with black representing the lowest elevation and white the highest.
79 |
80 | The first type, where Z goes from -1 to 1, is the type that is the most popular nowadays. Usually this texture is applied to a surface, so that the normals on the texture are relative to the interpolated (shading) normal of the surface itself. So, for example, a curved surface made of polygons is rendered. At a given surface location (typically) the shading normal is interpolated from vertex normals, defining the normal (Z) direction of the surface, along with X and Y basis vectors. This is known as ["tangent space"](https://80.lv/articles/tutorial-types-of-normal-maps-common-problems/). The normal is retrieved from the normals texture and modifies the surface normal.
81 |
82 | This first type has a few variants to it. One is that Y direction on the texture can be up or down. That is, the normals texture can be thought of as "X to the right, Y up" for how its normal is interpreted. A normal of, say, (0.2, 0.3, 0.933) can be thought of as pointing 0.2 to the right, 0.3 up. That is the OpenGL interpretation. The normal's Y direction could also be considered as pointing down, i.e., think of the upper left corner as the origin and Y pointing down. This is the DirectX interpretation. See [this page](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) for more information and examples, and search [this page](https://80.lv/articles/tutorial-types-of-normal-maps-common-problems/) for "invert" for a wider view. OpenGL and DirectX are two major different forms you'll run across, i.e., no one reverses the X direction normally. If you use the wrong type of texture in a renderer you will see artifacts such as objects looking like they're being lit from below, for example. The glTF file format requires the OpenGL style normals texture, USD assumes OpenGL but can use scale and bias settings to support DirectX. Currently the analysis program does not have code to tell OpenGL-style files from DirectX-style files. I have some ideas how to guess using statistics as to which format a normals texture is, but haven't examined the problem yet.
83 |
84 | For this first type, all (8-bit) values map from value 0 through 255 to the range -1.0 to 1.0. However, if you think about putting normals on a surface, a value where Z is less than 0.0 makes little sense. It would define a normal pointing into the surface, but the surface itself is at best thought of as a displaced heightfield (faked by using a normals texture instead), so there should not be any overhangs or other geometry where the Z value could go negative. One of the checks the analysis mode "-a" does is checks if the Z value is ever lower than 0.0. If it does, this is flagged as a problem.
85 |
86 | The second type of normals texture, what I call "Z-zero", maps the 0 to 255 8-bit blue channel value to the range 0.0 to 1.0, instead of -1.0 to 1.0. There then can be no negative values for Z, and the precision of the Z value can be one bit higher. This all sounds good, but this format is (in my experience) now rarely seen. If anyone understands this evolutionary step, let me know. The reality is that this format is not supported in glTF. It can be supported in USD by using bias and scale values. Again, see [this page](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) for USD examples and more information. It is difficult to simply look at a "Z-zero" texture and tell it apart from a normals texture of the (now-popular) first type, especially if the bumps are not sharp. By analyzing the Z values of the input texture and seeing whether they are negative and whether the (X,Y,Z) triplet formed has a vector length of nearly 1.0, we can usually tell these two types apart. That is, the program tries both conversions, standard and Z-zero, and sees which of the two normals formed is approximately 1.0 in length. This determination is the major reason this program exists, as sometimes incorrect textures slip through into test suites.
87 |
88 | Another problem sometimes seen with both types of normals texture is that just the X and Y values (ignoring Z) form a vector that is longer than 1.0. This can happen due to (poor) resizing or filtering of normals textures, slightly incorrect conversion formulas, or who knows what else. The analysis notes how many (X,Y) vectors are too long.
89 |
90 | The first two types, "standard" and "Z-zero", map red and green to X and Y in the same way, with the possible reversal of Y for the DirectX style texture. Given that Z cannot be a negative value (for most applications), some applications forego saving a Z value channel at all. The Z value is simply derived by sqrt(1.0 - (X\*X + Y\*Y)). For example, the [LabPBR Material](https://shaderlabs.org/wiki/LabPBR_Material_Standard) used for Minecraft modding specifies the texture holding normals as X and Y in red and green (in DirectX format), ambient occlusion is stored in the blue channel, and alpha holds the heightfield texture. The analyze mode attempts to identify these types of textures by looking at the Z values and see if the vector XYZ forms unit (length of 1.0) normals or if the Z's are independent values. If the latter, these normals textures are called "XY-only" textures in the analysis report.
91 |
92 | A few heuristics are used to classify textures that don't clearly fall into a single category. The analysis will give one or more reasons why a texture is categorized as a particular type. Two tolerances, -etol and -etolxy, can be adjusted to be less or more strict as to what falls in a category. The '-etol' option specifies a percentage of normal lengths that can be a fair bit far from 1.0 before the texture is categorized as not being a standard normals texture. The '-etolxy' option gives how much the average of all of the X or Y values can vary from 0.0, which is usually the average for most normals textures formed. If these averages are off by more than 0.1 (the default), the input image may not even be an XY texture.
93 |
94 | There are other variants of using XYZ coordinates, such as ["best fit normals" (BFN)](https://sebh-blog.blogspot.com/2010/08/cryteks-best-fit-normals.html). As we've seen, storing the Z value is superfluous, adding no real information, since it can be derived from X and Y. XYZ define a sphere (or hemisphere) of length 1, meaning a huge number of XYZ triplets are invalid, what this program fixes. It means that, of the 256-cubed (16 million) possible 8-bit RGB triplets storable in a texel, only 51,040 (0.3%) form valid XYZ triplets. BFN lets all RGB triplets be valid (assuming blue is mapped to Z 0 to 1, or B < 128 is not allowed), with the idea being that the corresponding XYZ formed will always then be normalized. This program currently ignores the possibility that the normals texture is a BFN. Detection is perhaps possible by trying to convert back to a heightfield and see if the result is valid by some metric.
95 |
96 | A different class of normals texture is the heightfield texture, often called a bump or height map. The format is usually a single channel (such as the red channel) or a grayscale image. Black is usually the lowest level, white the highest elevation. Analysis tries some basic testing to see if a texture may be a heightfield texture. Heightfield textures are often used to create normals textures, e.g., [GIMP has this functionality](https://docs.gimp.org/en/gimp-filter-normal-map.html). There is a scale (not stored in the image) that needs to be provided for the heightfield so that the conversion can be done. I have occasionally seen heightmap textures get intermingled with normals textures, so the program tries to identify these.
97 |
98 | You can also output a heatmap of where errors are in your input image file (except for heightfields). Specify '-oheatmap' says to make the output file show these. The red channel is set to 255 wherever the texel has X and Y values that give a vector length > 1.0 + epsilon (of 1/255), red of 128 if the length is just barely above 1.0. For input normals textures categorized as standard or Z-zero, the green channel shows a medium green when the texel's XYZ normal length, converted to RGB, is off by two color levels, and shows full green when 3 levels or more. The blue channel shows negative Z values for standard input normal textures, starting with 128 for slightly negative scaling up to 255 for Z stored as -1. Remember that "XY-only" normal textures can be forced to be interpreted as standard by '-izneg', which will then highlight the XYZ length differences found.
99 |
100 | Here's an example run to generate a heatmap of the test file "ntp_resize_512.png", which has normalization errors due to resizing, and the result:
101 | ```sh
102 | .\x64\Release\NormalTextureProcessor.exe -idir test_files\Standard -odir test_files\output_Heatmap -oall -a -v -oheatmap ntp_resize_512.png
103 | ```
104 | 
105 |
106 | You can also dump out the bad pixels found in a file, or all pixel values and their conversions. Add the '-csve' argument to dump to the output directory a comma-separated-value file of texels that are not "perfect" (roundtrippable - see "Algorithms"), or '-csv' to dump all texel values. You also _must_ specify the input file type (-izneg|-izzero|-ixy) as the CSV file is generated as the file is read in, before any analysis. Here's an example:
107 | ```sh
108 | .\x64\Release\NormalTextureProcessor.exe-idir test_files\Standard -odir test_files\output_Standard -oall -a -v squiggles_normalmap_online_zneg.png -izneg -csve
109 | ```
110 | which will generate the file test_files/output_Standard/squiggles_normalmap_online_zneg.csv. The rightmost columns will show what sort of bad thing is happening with each texel: whether it's near (1 or 2 levels) or far (X) away from being an XY length below 1.0, whether Z is as expected or not, and whether the stored Z was negative.
111 |
112 | It's important to look at the normal length vs. variation in the separate XYZ values. An an example, say a texel's RGB is (127,0,139). XYZ=(-0.00392, -1.0, 0.09019). The vector length is 1.004066. If you cleaned this pixel, holding X and Y constant, the blue value would change, to (127,0,128). The difference in blue is large: 139 vs. 128, 0.09 vs. 0.00. The problem is that an extreme Y value, where G=0 gives Y=-1.0, forces Z to be as small as possible, B=128.
113 |
114 | This program considers (127,0,139) to be correct. It tests by varying the RGB values of the triplet by +/-1, converts, and looks at the normal lengths formed for all nine triplet combinations. If at least one triplet gives a normal length of less than or equal to 1.0 and at least one has normal length of greater than or equal to 1.0, then the triplet is considered valid. If you want something more exact, change the code! Only if a triplet gives a normal outside this range is the texture flagged as in need of cleaning.
115 |
116 | ### Cleanup and Conversion
117 |
118 | In addition to analysis, this program cleans up and convert to different formats. Specify an output directory with '-odir _out_directory_name_'. The output file name will match the input file name, though always uses PNG for output. Be careful: if the input and output directories are the same or both are not specified (and so match), texture files will be written over in place.
119 |
120 | Typical operations include:
121 | * Clean up the normals in a texture. Use option '-oclean' to output only those files that need cleanup, '-oall' to output all files. Given a particular type of RGB texture, the program makes sure the normals are all properly normalized and that no Z values are negative.
122 | * If you want to maintain the (possibly negative) sign of the Z value, use the '-allownegz' option.
123 | * If you set the input file type (-izneg|-izzero|-ixy), '-oclean' will clean all files found to not be of that type.
124 | * Convert between formats.
125 | * OpenGL to DirectX, standard to Z-zero, or vice versa. OpenGl-style is the default, use '-idx' to note that the input files are DirectX-style (does not affect heightfields), '-odx' to set that you want to output in DirectX-style.
126 | * To export to the Z-Zero format, where Z goes from 0 to 1 instead of -1 to 1, use '-ozzero'.
127 | * Heightfields can be converted to RGB textures of any type, using the options above.
128 | * To force all input files to be treated as heightfields, use '-ihf'.
129 | * To specify how the input heightfield data is scaled, use '-hfs #', where you give a scale factor. The value is 5.0 by default.
130 | * By default heightfields are considered to repeat, meaning that along the edges heights "off the texture" are taken from the opposite edge. TBD: do the borders wrap or are they "doubled" or something else, such as maintaining slope. '-hborder'
131 |
132 | This cleanup process comes with a caveat: garbage in, garbage out. If your normals textures have XY values that are trustworthy and you know the Z values are wonky for some reason, this tool can properly fix the Z values, deriving from XY. If your normals are just plain unnormalized in general, where X, Y, and Z are the right direction but the wrong length (see "BFN"), this tool will not properly fix your texture, since it assumes X and Y are their proper lengths and Z was badly derived. The program itself cannot determine which type of error is occurring, so it assumes that only the Z value is invalid. Also, if an image is cleaned, currently _all_ normals will be examined cleaned and made "roundtrippable" (see below). Basically, if bad normals are found, it's assumed all Z values should be rederived.
133 |
134 | Tools such as [BeyondCompare](https://www.scootersoftware.com/) are worthwhile for comparing the original image with its modified version. You can hover over individual texels and see how the values have changed. Here's an example diff, with the tolerance set to 2 texel levels difference in any channel; red is more, blue is equal or less, gray is no difference.
135 |
136 | 
137 |
138 | In this example, from [glTF-sample-assets](https://github.com/KhronosGroup/glTF-Sample-Assets/blob/main/Models/ClearcoatWicker/glTF/clearcoat_normal.png), you can see that the more curved areas are far off. For example, texel X:155, Y:48 has an RGB value of (100,145,240). This gives an XYZ normal of (-0.216,0.137,0.882), which has a normal length of 0.918, well short of 1.0. Assuming X and Y are valid and the blue channel was formed incorrectly, we recompute Z as 0.967 to make a normal of length 1.0, which translates to a blue value of 251.
139 |
140 | Does it matter? Using the original vs. corrected clearcoat_normal.png with the other files in the [ClearcoatWicker](https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/ClearcoatWicker/glTF) test directory, using Don McCurdy's (excellent) [glTF Viewer](https://gltf-viewer.donmccurdy.com/), we get these two renderings, diff'ed:
141 |
142 | 
143 |
144 | Visually the changes are subtle. In the middle along the vertical seam the white highlight shapes are different. In the indentations along the seam at the very bottom, the original image has darker ones.
145 |
146 | In other tests, such as the [StainedGlassLamp](https://github.com/KhronosGroup/glTF-Sample-Assets/tree/main/Models/StainedGlassLamp), where the goal is to accurately capture the real-world model, fixing the four normals textures for this model also results in slightly different renderings:
147 |
148 | 
149 |
150 | Long and short, storing the normals incorrectly is a source of error. This program can, at the minimum, tip you off when your normals are not or poorly normalized, and provide properly normalized textures by correcting the Z values.
151 |
152 | ## Algorithms
153 |
154 | The key thing is to get the conversion right. Here's how to go from an 8-bit color channel value "rgb" to the floating-point range -1.0 to 1.0 for "xyz":
155 | ```sh
156 | x = ((float)r / 255.0f) * 2.0f - 1.0f;
157 | y = ((float)g / 255.0f) * 2.0f - 1.0f;
158 | z = ((float)b / 255.0f) * 2.0f - 1.0f;
159 | ```
160 | In [UsdPreviewSurface](https://openusd.org/release/spec_usdpreviewsurface.html#texture-reader) terms, the 2.0f is the scale, the -1.0f is the bias. Note that the RGB triplet is assumed to map directly to linear values -1.0 through 1.0, there's no sRGB version or similar. In other words, the data is considered to be "just numbers" and not some gamma-corrected color.
161 |
162 | If you want to go to the range 0.0 to 1.0, it's simpler still. Here's Z:
163 | ```sh
164 | z = (float)(b) / 255.0f
165 | ```
166 |
167 | If you want to compute Z from X and Y, it's:
168 | ```sh
169 | z = sqrt(1.0f - (x * x + y * y));
170 | ```
171 | In practice I recommend always deriving Z from X and Y in your shaders, even if Z is available, as a considerable percentage of normals textures examined often have bad Z (blue) values stored.
172 |
173 | Getting the rounding right for the return trip is important. Here's that:
174 | ```sh
175 | r = (unsigned char)(((x + 1.0f)/2.0f)*255.0f + 0.5f);
176 | g = (unsigned char)(((y + 1.0f)/2.0f)*255.0f + 0.5f);
177 | b = (unsigned char)(((z + 1.0f)/2.0f)*255.0f + 0.5f);
178 | ```
179 | The final 0.5f rounding value on each line is important for consistency of conversion, to make roundtripping work properly. I believe that not adding in the 0.5f and rounding is the source of error in various normals textures creators. See the analysis below of GIMP's (incorrect) formula for more explanation.
180 |
181 | The code above shows where the bias of 1.0 and scale of 2.0 are applied. The equations can be rearranged to:
182 | ```sh
183 | r = (unsigned char)((0.5f*x + 0.5f)*255.0f + 0.5f);
184 | g = (unsigned char)((0.5f*x + 0.5f)*255.0f + 0.5f);
185 | b = (unsigned char)((0.5f*x + 0.5f)*255.0f + 0.5f);
186 | ```
187 |
188 | For Z-zero to the blue channel, the conversion is simply:
189 | ```sh
190 | b = (unsigned char)(z*255.0f + 0.5f);
191 | ```
192 |
193 | See the code in the SCRATCHPAD section of NormalTextureProcessor.cpp for a roundtrip test and other procedures.
194 |
195 | This program aims to make it so that rgb -> xy -> derive z from xy -> rgb will give the same rgb's. This is not a given. For example, say you have computed an XYZ triplet:
196 | ```sh
197 | x = -0.423667967
198 | y = 0.271147490
199 | z = 0.864282646
200 | ```
201 | This vector has a length of 0.99999999988 - very close to 1.0. The Z value is precise.
202 |
203 | If you convert each channel directly to 8-bit rgb, you get:
204 | ```sh
205 | r = 73 // actually 73.9823342075, drop the fraction
206 | g = 162 // 162.571304975, drop the fraction
207 | b = 238 // 238.196037365, drop the fraction
208 | ```
209 | Convert rgb back to XYZ, you get similar values to those found before, but not quite the same:
210 | ```sh
211 | x = -0.4274509 // a little higher magnitude than -0.423667967
212 | y = 0.2705882 // a tiny bit lower than 0.271147490
213 | z = 0.8666666 // a bit higher than 0.864282646
214 | ```
215 | The vector formed is also less precise, 1.0035154. This is to be expected with 8-bit numbers representing XYZ values. Say we immediately convert this xyz back to rgb:
216 | ```sh
217 | r = 73 // in fact, 73.50001025, drop the fraction, right in the middle of the unsigned char
218 | g = 162 // 162.4999955, drop the fraction
219 | b = 238 // 238.4999915, drop the fraction. Came from converting z=0.8666666
220 | ```
221 | No surprise, each floating-point value is right in the middle of the "range" for each RGB value, with each fraction being near 0.5. This is a good reality check that our functions for rgb -> xyz and xyz -> rgb are solid in their own right.
222 |
223 | The problem happens if we now derive Z from x and y, instead of using the converted z value of 0.8666666:
224 | ```sh
225 | z = sqrt(1.0 - (-0.4274509*-0.4274509 + 0.2705882*0.2705882))
226 | z = 0.8625936 // different than 0.8666666 saved in the b channel
227 | ```
228 |
229 | Converting this xyz, with the new z value computed from the new vector length, back to rgb, we get:
230 | ```sh
231 | r = 73 // again 73.50001025
232 | g = 162 // again 162.4999955
233 | b = 237 // 237.980684, dropping the fraction gives 237
234 | ```
235 | This b value is one level less than the original 238 we computed for it. This difference happens because we lose precision converting from xyz to rgb the first time, so that when we convert from rgb the second time and then compute Z from X and Y, the X and Y being different causes Z to be enough different that it changes a level.
236 |
237 | What is interesting at this point is that "rgb -> xy -> derive z from xy -> rgb" is now stable. The values "rg" never change, so X and Y don't change, so the Z derived from them is also the same, giving the same "b" value when converted back. With b=237, Z is 0.8588235. The normal length of this new XYZ is 0.9967497, which is in this case ever so slightly better than 1.0035154 (it's 0.0032503 away from 1.0, vs. 0.0035154).
238 |
239 | What roundtripping implies is that the length of the normal being close to 1.0 is the metric that controls things. If you convert r and g to X and Y, then use these to compute Z and convert to "b", you have the same "b", a "b" that gives a normal as close as possible to 1.0, given r and g. The alternative is to treat X, Y, and Z as entirely separate channels having nothing to do with each other once we have the values in floating point. However, doing so means round-tripping doesn't work. This in turn means (admittedly usually tiny) differences with shaders: one shader uses Z computed from the stored b, another uses Z computed from r and g, and these Z's are sometimes off by a texel level.
240 |
241 | This program aims to provide stable rgb triplets, ones where if the Z value is derived from X and Y, the "b" value is the same as the original value. Internally, this means that we always derive the Z value from the 8-bit r and g values, as shown, so that the result "b" value is the same as the derived "b". When reading in normals rgb textures, the program always derives the Z value as explained, so gives consistent results. That is, if you clean up a normals texture, the program's clean image will not change if you clean it again. Where this principle affects the code a bit more is when using a heightfield to compute an XYZ normal. As shown above, to make a roundtrippable rgb triplet, we have to convert XY to rg, then rg back to let's call it XY', the XY values that come from the 8-bit precision r and g values. Then Z is computed as usual and the b value created from it.
242 |
243 | ## Creator Testing
244 |
245 | Ideally, it would be great if all tools creating normals textures did things perfectly. I examined some tools' code to see if they were correct.
246 |
247 | ### GIMP
248 |
249 | [GIMP](https://www.gimp.org/) has a tool to make [normals textures from heightfields](https://docs.gimp.org/en/gimp-filter-normal-map.html). It's easy to use, with lots of options for different mappings. The code is [here](https://code.google.com/archive/p/gimp-normalmap/source/default/source) and [here](https://github.com/RobertBeckebans/gimp-plugin-normalmap), with normalmap.c appearing to be the key piece.
250 |
251 | Looking at the code, I noticed a problem with conversion back to RGB. [Lines 1056-1058 of normalmap.c](https://github.com/RobertBeckebans/gimp-plugin-normalmap/blob/master/normalmap.c#L1056) are:
252 | ```sh
253 | *d++ = (unsigned char)((n[0] + 1.0f) * 127.5f);
254 | *d++ = (unsigned char)((n[1] + 1.0f) * 127.5f);
255 | *d++ = (unsigned char)((n[2] + 1.0f) * 127.5f);
256 | ```
257 | This is not quite right, as there's no rounding value added in. The code should be:
258 | ```sh
259 | *d++ = (unsigned char)((n[0] + 1.0f) * 127.5f + 0.5f);
260 | *d++ = (unsigned char)((n[1] + 1.0f) * 127.5f + 0.5f);
261 | *d++ = (unsigned char)((n[2] + 1.0f) * 127.5f + 0.5f);
262 | ```
263 | Proof by examination: say I convert X=-0.999 and X=0.999. By the original code I would get R=0 and R=254. I would expect symmetric results, R rounding to the same limits. With the corrected formula I get R=0 and R=255, maintaining symmetry. Another proof is that X=0 converts to 127.5 in the original code, 128.0 in the new. The value 127.5 has a logic to it, being exactly between 0.0 and 255.0, but the fraction 0.5 is then dropped instead of rounded. By adding 0.5 we get 128.0, which in floating point terms is right on the border between 127 and 128 (that is, level 127 goes from 127.0 to 127.99999...). So, adding 0.5 and rounding is correct, as 127 and 128 is the dividing line between negative and positive X (or Y or Z) values.
264 |
265 | What makes this all a bit confusing is that X=0.0 doesn't have an exact 8-bit RGB equivalent, since 127/128 is the dividing line. Whenever I see a B value of 127 stored in a normals texture, I suspect the round-off was done incorrectly, because 127 converts to a slightly negative value, -0.003921.
266 |
267 | Note that my corrected code here would affect all channels, R, G, and B, so that the XY values stored in RG are also slightly off. With the original, incorrect code, they're a bit more negative than expected because of the missing roundoff value.
268 |
269 | ### Normals Online
270 |
271 | [Normalmap Online](https://cpetry.github.io/NormalMap-Online/), [github](https://github.com/cpetry/NormalMap-Online), has interactive conversion of heightfields to normals textures. The results are good, with normals appearing to be properly formed. The code [here](https://github.com/cpetry/NormalMap-Online/blob/gh-pages/javascripts/shader/NormalMapFromPicturesShader.js#L53) and [here](https://github.com/cpetry/NormalMap-Online/blob/gh-pages/javascripts/shader/NormalMapShader.js#L83) looks fine, it normalizes the data before assignment. I assume gl_FragColor properly rounds XYZ values fed to it.
272 |
273 | ## Resources
274 |
275 | Some resources I've found useful:
276 | * [Types of Normal Maps & Common Problems](https://80.lv/articles/tutorial-types-of-normal-maps-common-problems/) - a useful page, talking about what uses OpenGL vs. DirectX format by default, XY format, and other aspects, such as not gamma-correcting your normals textures (something I currently don't test for).
277 | * [Normalmap Online](https://cpetry.github.io/NormalMap-Online/), [github](https://github.com/cpetry/NormalMap-Online) - a web-based normal map creation tool, it takes heightfields and converts to normals textures. By default, Z is mapped from 0 to 1 (old style), but there is a "Z Range" option (that I helped add) that lets you choose -1 to 1. A bit confusing, but what you do is click on the "wavy rings" heightmap image itself. A file dialog comes up and you load your own image. You can then see the RGB texture formed, and the texture applied to a cube in the right. Mouse-drag to change the view of the cube. Lots of options, including true displacement, so you can see the difference between that and just normals textures. Note: the precision of the conversion is not perfect, with some computed normal lengths varying up to four levels from the correct answer.
278 | * [GIMP conversion](https://docs.gimp.org/en/gimp-filter-normal-map.html) - how to make normals textures from heightfields in GIMP.
279 | * [Blender's documentation](https://docs.blender.org/manual/en/2.79/render/blender_render/textures/properties/influence/bump_normal.html) - pointers to the format used and baking polygon meshes to normals textures.
280 | * Other tools to test: [ShaderMap](https://shadermap.com/), [DeepBump](https://hugotini.github.io/deepbump.html), [PixelGraph](https://github.com/null511/PixelGraph-Release); more recommendations appreciated! Fixing the tools themselves is the best long-term solution.
281 | * [glTF 2.0 specification](https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html) - has much about normals textures. I [filed a pull request](https://github.com/KhronosGroup/glTF-Sample-Assets/pull/114) submitting a large number of fixed normals textures for the glTF Sample Assets repository.
282 | * [USD Normals Texture Bias And Scale](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) - a set of test normals textures of different types in a USD file, along with copious documentation of how each cube is formed. I made this test. For USD, I [filed a bug](https://github.com/PixarAnimationStudios/OpenUSD/issues/3013) reporting that one (of the two) normalMap.png images in the OpenUSD repository is off.
283 | * [UsdPreviewSurface](https://openusd.org/release/spec_usdpreviewsurface.html#texture-reader) - the USD specification's basic material. Search on "normal3f" to see more about how to specify it.
284 | * [_Survey of Efficient Representations for Independent Unit Vectors_](https://jcgt.org/published/0003/02/01/) - Storing normals as XYZ triplets is wasteful, since Z could just be computed from X and Y in most usages. Even storing just X and Y gives pairs of numbers that are unusable, forming vectors longer than 1.0, and the precision is poorly distributed for the rest. This paper and code gives some better alternate representations.
285 |
286 | ## License
287 |
288 | MIT license. See the [LICENSE](https://github.com/erich666/NormalTextureProcessor/blob/main/LICENSE) file in this directory.
289 |
290 | ## Acknowledgements
291 |
292 | Thanks to Nick Porcino for the quick check on my conversion equations. Thanks to Koen Vroeijenstijn for discussions about 16-bit PNG formats, and John Stone for even higher precision representations.
293 |
294 | ## Roadmap
295 |
296 | Potential tasks (no promises! and, suggestions welcome):
297 |
298 | - [ ] Figure out way (if any) to tell if a texture is OpenGL or DirectX oriented. I suspect there's some curvature analysis, perhaps converting into a heightfield, that could be done. For example, take differences between texels horizontally and vertically and see the correspondence - bumps are more likely than hyperboloids. Or maybe it's not possible (well, it certainly isn't if there's no Y variance).
299 | - [ ] Convert back from normals textures to heightfields. Not sure this is useful, but might be worth adding, for visualization and analysis. Sources of information: [ShaderToy](https://www.shadertoy.com/view/XcjXDc), [Stannum](https://stannum.io/blog/0IwyJ-) and [code](https://stannum.io/blog/data/debump.c), [tweet](https://mastodon.gamedev.place/@castano/110396678556411922), [solver presentation](https://www.activision.com/cdn/research/MaterialAdvancesInWWII.pdf), [paper](https://webdocs.cs.ualberta.ca/~vis/courses/CompVis/readings/photometric/FrankotIntegrpami88.pdf) _(thanks to Ignacio Castaño for these links)_
300 | - [ ] Ignore texels that are black or white (probably unused in texture, except for heightfields). Adjust statistics, CSV output, etc.
301 | - [ ] Support 16-bit PNG files fully, input and output. Also properly convert channel values that have less than 8 bits.
302 | - [ ] Ignore texels with alpha values of 0.
303 | - [ ] Recursively examine directories and clean up normals texture files, searching by file type. If the output directory is specified, cleaned or heatmap files would be put there, without the corresponding subdirectory paths (possibly overwriting results, if name collisions).
304 |
--------------------------------------------------------------------------------
/image_iterator.cpp:
--------------------------------------------------------------------------------
1 | // Aseprite TGA Library
2 | // Copyright (C) 2020 Igara Studio S.A.
3 | //
4 | // This file is released under the terms of the MIT license.
5 | // Read LICENSE.txt for more information.
6 |
7 | #include "tga.h"
8 |
9 | namespace tga {
10 | namespace details {
11 |
12 | ImageIterator::ImageIterator()
13 | : m_image(nullptr)
14 | {
15 | }
16 |
17 | ImageIterator::ImageIterator(const Header& header, Image& image)
18 | : m_image(&image)
19 | , m_x(header.leftToRight() ? 0: header.width-1)
20 | , m_y(header.topToBottom() ? 0: header.height-1)
21 | , m_w(header.width)
22 | , m_h(header.height)
23 | , m_dx(header.leftToRight() ? +1: -1)
24 | , m_dy(header.topToBottom() ? +1: -1)
25 | {
26 | calcPtr();
27 | }
28 |
29 | bool ImageIterator::advance()
30 | {
31 | m_x += m_dx;
32 | m_ptr += m_dx*m_image->bytesPerPixel;
33 |
34 | if ((m_dx < 0 && m_x < 0) ||
35 | (m_dx > 0 && m_x == m_w)) {
36 | m_x = (m_dx > 0 ? 0: m_w-1);
37 | m_y += m_dy;
38 | if ((m_dy < 0 && m_y < 0) ||
39 | (m_dy > 0 && m_y == m_h)) {
40 | return true;
41 | }
42 | calcPtr();
43 | }
44 | return false;
45 | }
46 |
47 | void ImageIterator::calcPtr()
48 | {
49 | m_ptr =
50 | m_image->pixels
51 | + m_image->rowstride*m_y
52 | + m_image->bytesPerPixel*m_x;
53 | }
54 |
55 | } // namespace details
56 | } // namespace tga
57 |
--------------------------------------------------------------------------------
/readme_clearcoat_diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/readme_clearcoat_diff.png
--------------------------------------------------------------------------------
/readme_heatmap.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/readme_heatmap.png
--------------------------------------------------------------------------------
/readme_lamp_render_diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/readme_lamp_render_diff.png
--------------------------------------------------------------------------------
/readme_sample.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/readme_sample.png
--------------------------------------------------------------------------------
/readme_wicker_render_diff.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/readme_wicker_render_diff.png
--------------------------------------------------------------------------------
/readtga.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2021, Eric Haines
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 | THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | #include "stdafx.h"
29 | #include "readtga.h"
30 |
31 | #include
32 |
33 | #include
34 |
35 | // calls https://github.com/aseprite/tga
36 |
37 | //Decode from disk to raw pixels with a single function call
38 | // return 0 on success
39 | int readtga(progimage_info *im, wchar_t *filename, LodePNGColorType colortype)
40 | {
41 | im->width = im->height = 0;
42 |
43 | FILE* f;
44 | _wfopen_s(&f, filename, L"rb");
45 | tga::StdioFileInterface file(f);
46 | tga::Decoder decoder(&file);
47 | tga::Header header;
48 | if (!decoder.readHeader(header))
49 | return 102;
50 |
51 | bool match = false;
52 | int channels_in = header.bytesPerPixel();
53 | switch (channels_in) {
54 | case 1:
55 | if ( colortype == LCT_GREY ) {
56 | match = true;
57 | }
58 | break;
59 | case 3:
60 | if (colortype == LCT_RGB) {
61 | match = true;
62 | }
63 | break;
64 | case 4:
65 | if (colortype == LCT_RGBA) {
66 | match = true;
67 | }
68 | break;
69 | default:
70 | // unsupported file type, we assume
71 | return 104;
72 | }
73 |
74 | im->width = (int)header.width;
75 | im->height = (int)header.height;
76 |
77 | tga::Image image;
78 | image.bytesPerPixel = header.bytesPerPixel();
79 | image.rowstride = header.width * header.bytesPerPixel();
80 |
81 | std::vector buffer(image.rowstride * header.height);
82 | image.pixels = &buffer[0];
83 |
84 | if (!decoder.readImage(header, image, nullptr))
85 | return 103;
86 |
87 | // Optional post-process to fix the alpha channel in
88 | // some TGA files where alpha=0 for all pixels when
89 | // it shouldn't.
90 | decoder.postProcessImage(header, image);
91 |
92 | if (match) {
93 | im->image_data = buffer;
94 | }
95 | else {
96 | int channels_out = 0;
97 |
98 | // color types don't match - convert from one to the other
99 | switch (channels_in) {
100 | case 1:
101 | // gray to RGB or RGBA
102 | channels_out = (colortype == LCT_RGB) ? 3 : 4;
103 | break;
104 | case 3:
105 | // RGB to gray or RGBA
106 | channels_out = (colortype == LCT_RGBA) ? 1 : 4;
107 | break;
108 | case 4:
109 | // RGBA to gray or RGB (ignore alpha, I guess...)
110 | channels_out = (colortype == LCT_RGB) ? 3 : 1;
111 | break;
112 | }
113 | int num_pixels = header.width * header.height;
114 | im->image_data.resize(channels_out * num_pixels);
115 | int i;
116 | unsigned char* src_data = &buffer[0];
117 | unsigned char* dst_data = &im->image_data[0];
118 | switch (channels_in) {
119 | case 1:
120 | // gray to RGB or RGBA
121 | for (i = 0; i < num_pixels; i++) {
122 | if (channels_out == 4) {
123 | *dst_data++ = *src_data;
124 | }
125 | *dst_data++ = *src_data;
126 | *dst_data++ = *src_data;
127 | *dst_data++ = *src_data++;
128 | }
129 | break;
130 | case 3:
131 | // RGB to gray or RGBA
132 | if (channels_out == 4) {
133 | for (i = 0; i < num_pixels; i++) {
134 | *dst_data++ = *src_data++;
135 | *dst_data++ = *src_data++;
136 | *dst_data++ = *src_data++;
137 | *dst_data++ = 255;
138 | }
139 | }
140 | else {
141 | // RGB to gray? Why? Is the incoming RGB image actually gray?
142 | assert(0);
143 | for (i = 0; i < num_pixels; i++) {
144 | *dst_data++ = *src_data++;
145 | // skip green and blue
146 | src_data++;
147 | src_data++;
148 | }
149 | }
150 | break;
151 | case 4:
152 | // RGBA to gray or RGB (ignore alpha, I guess...)
153 | if (channels_out == 3) {
154 | for (i = 0; i < num_pixels; i++) {
155 | *dst_data++ = *src_data++;
156 | *dst_data++ = *src_data++;
157 | *dst_data++ = *src_data++;
158 | src_data++; // skip alpha
159 | }
160 | }
161 | else {
162 | // RGBA to gray? Why? Is the incoming RGB image actually gray?
163 | assert(0);
164 | for (i = 0; i < num_pixels; i++) {
165 | *dst_data++ = *src_data++;
166 | // skip green and blue and alpha
167 | src_data++;
168 | src_data++;
169 | src_data++;
170 | }
171 | }
172 | break;
173 | }
174 | }
175 |
176 | return 0;
177 | }
178 |
179 | // same as PNG's
180 | //void readtga_cleanup(int mode, progimage_info *im)
181 | //{
182 | // // mode was important back when libpng was in use
183 | // if ( mode == 1 )
184 | // {
185 | // im->image_data.clear();
186 | // }
187 | //}
188 |
189 | int readtgaheader(progimage_info* im, wchar_t* filename, LodePNGColorType& colortype)
190 | {
191 | im->width = im->height = 0;
192 |
193 | FILE* f;
194 | _wfopen_s(&f, filename, L"rb");
195 | tga::StdioFileInterface file(f);
196 | tga::Decoder decoder(&file);
197 | tga::Header header;
198 | if (!decoder.readHeader(header))
199 | return 102;
200 |
201 | im->width = (int)header.width;
202 | im->height = (int)header.height;
203 |
204 | switch (header.bytesPerPixel()) {
205 | case 1:
206 | colortype = LCT_GREY;
207 | break;
208 | default:
209 | assert(0);
210 | case 3:
211 | colortype = LCT_RGB;
212 | break;
213 | case 4:
214 | colortype = LCT_RGBA;
215 | break;
216 | }
217 |
218 | fclose(f);
219 |
220 | return 0;
221 | }
222 |
223 | //============================
224 | // Yes, these should be in yet another separate file, but alas
225 |
226 | int readImage(progimage_info* im, wchar_t* filename, LodePNGColorType colortype, int imageFileType)
227 | {
228 | if (imageFileType == 1) {
229 | return readpng(im, filename, colortype);
230 | } else if (imageFileType == 2) {
231 | return readtga(im, filename, colortype);
232 | }
233 | assert(0);
234 | // unknown image type
235 | return 1;
236 | }
237 |
238 | void readImage_cleanup(int mode, progimage_info* im)
239 | {
240 | // same for TGA
241 | readpng_cleanup(mode, im);
242 | }
243 |
244 | int readImageHeader(progimage_info* im, wchar_t* filename, LodePNGColorType& colortype, int imageFileType)
245 | {
246 | if (imageFileType == 1) {
247 | return readpngheader(im, filename, colortype);
248 | }
249 | else if (imageFileType == 2) {
250 | return readtgaheader(im, filename, colortype);
251 | }
252 | assert(0);
253 | // unknown image type
254 | return 999;
255 | }
256 |
257 |
--------------------------------------------------------------------------------
/readtga.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011, Eric Haines
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 | THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | #pragma once
29 |
30 | #include "lodepng.h"
31 | #include "rwpng.h"
32 | #include "tga.h"
33 |
34 | int readtga(progimage_info *mainprog_ptr, wchar_t *filename, LodePNGColorType colortype /*= LCT_RGBA*/);
35 | //void readtga_cleanup(int free_image_data, progimage_info *mainprog_ptr);
36 | int readtgaheader(progimage_info* im, wchar_t* filename, LodePNGColorType& colortype);
37 |
38 | int readImage(progimage_info* im, wchar_t* filename, LodePNGColorType colortype, int imageFileType);
39 | void readImage_cleanup(int mode, progimage_info* im);
40 | int readImageHeader(progimage_info* im, wchar_t* filename, LodePNGColorType& colortype, int imageFileType);
41 |
--------------------------------------------------------------------------------
/rwpng.cpp:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011, Eric Haines
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 | THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | #include "stdafx.h"
29 | #include "rwpng.h"
30 |
31 | #include
32 |
33 | #include
34 |
35 | // from http://lodev.org/lodepng/example_decode.cpp
36 |
37 | //Decode from disk to raw pixels with a single function call
38 | // return 0 on success
39 | int readpng(progimage_info *im, wchar_t *filename, LodePNGColorType colortype)
40 | {
41 | //char filename[MAX_PATH];
42 | //dumb_wcharToChar(wfilename,filename);
43 |
44 | //decode
45 | unsigned int width, height;
46 | unsigned int error = lodepng::decode(im->image_data, width, height, filename, colortype);
47 |
48 | //if there's an error, display it
49 | if (error)
50 | {
51 | im->width = 0;
52 | im->height = 0;
53 | //std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl;
54 | return (int)error;
55 | }
56 |
57 | im->width = (int)width;
58 | im->height = (int)height;
59 |
60 | //the pixels are now in the vector "image", for color+alpha these are 4 bytes per pixel, ordered RGBARGBA..., use it as texture, draw it, ...
61 |
62 | return 0;
63 | }
64 |
65 | void readpng_cleanup(int mode, progimage_info *im)
66 | {
67 | // mode was important back when libpng was in use
68 | if ( mode == 1 )
69 | {
70 | im->image_data.clear();
71 | }
72 | }
73 |
74 | // return 0 if no error
75 | int readpngheader(progimage_info* im, wchar_t* filename, LodePNGColorType& colortype)
76 | {
77 | unsigned int width, height;
78 | std::vector buffer;
79 | lodepng::load_file(buffer, filename);
80 |
81 | colortype = LCT_RGBA;
82 | unsigned bitdepth = 8;
83 |
84 | LodePNGState state;
85 | lodepng_state_init(&state);
86 | state.info_raw.colortype = colortype;
87 | state.info_raw.bitdepth = bitdepth;
88 | // reads header and resets other parameters in state->info_png
89 | state.error = lodepng_inspect(&width, &height, &state, buffer.empty() ? 0 : &buffer[0], (unsigned)buffer.size());
90 | unsigned int error = state.error;
91 | colortype = state.info_png.color.colortype;
92 |
93 | lodepng_state_cleanup(&state);
94 |
95 | //if there's an error, display it
96 | if (error)
97 | {
98 | im->width = 0;
99 | im->height = 0;
100 | //std::cout << "decoder error " << error << ": " << lodepng_error_text(error) << std::endl;
101 | return (int)error;
102 | }
103 |
104 | im->width = (int)width;
105 | im->height = (int)height;
106 |
107 | return 0;
108 | }
109 |
110 | // from http://lodev.org/lodepng/example_encode.cpp
111 |
112 | //Encode from raw pixels to disk with a single function call
113 | //The image argument has width * height RGBA pixels or width * height * channels
114 | // return 0 on success
115 | int writepng(progimage_info *im, int channels, wchar_t *filename)
116 | {
117 | //char filename[MAX_PATH];
118 | //dumb_wcharToChar(wfilename,filename);
119 |
120 | //Encode the image, depending on type
121 | unsigned int error = 1; // 1 means didn't reach lodepng
122 | if ( channels == 4 )
123 | {
124 | // 32 bit RGBA, the default
125 | error = lodepng::encode(filename, im->image_data, (unsigned int)im->width, (unsigned int)im->height, LCT_RGBA );
126 | }
127 | else if ( channels == 3 )
128 | {
129 | // 24 bit RGB
130 | error = lodepng::encode(filename, im->image_data, (unsigned int)im->width, (unsigned int)im->height, LCT_RGB );
131 | }
132 | else if (channels == 1)
133 | {
134 | // 8 bit grayscale
135 | error = lodepng::encode(filename, im->image_data, (unsigned int)im->width, (unsigned int)im->height, LCT_GREY);
136 | }
137 | else
138 | {
139 | assert(0);
140 | }
141 |
142 | //if there's an error, display it
143 | if (error)
144 | {
145 | //std::cout << "encoder error " << error << ": "<< lodepng_error_text(error) << std::endl;
146 | return (int)error;
147 | }
148 |
149 | return 0;
150 | }
151 |
152 |
153 | void writepng_cleanup(progimage_info *im)
154 | {
155 | im->image_data.clear();
156 | }
157 |
158 | progimage_info* allocateGrayscaleImage(progimage_info* source_ptr)
159 | {
160 | // allocate output image and fill it up
161 | progimage_info* destination_ptr = new progimage_info();
162 |
163 | destination_ptr->width = source_ptr->width;
164 | destination_ptr->height = source_ptr->height;
165 | destination_ptr->image_data.resize(destination_ptr->width * destination_ptr->height * 1 * sizeof(unsigned char), 0x0);
166 |
167 | return destination_ptr;
168 | }
169 |
170 | progimage_info* allocateRGBImage(progimage_info* source_ptr)
171 | {
172 | // allocate output image and fill it up
173 | progimage_info* destination_ptr = new progimage_info();
174 |
175 | destination_ptr->width = source_ptr->width;
176 | destination_ptr->height = source_ptr->height;
177 | destination_ptr->image_data.resize(destination_ptr->width * destination_ptr->height * 3 * sizeof(unsigned char), 0x0);
178 |
179 | return destination_ptr;
180 | }
181 |
182 | void copyOneChannel(progimage_info* dst, int channel, progimage_info* src, LodePNGColorType colortype)
183 | {
184 | int row, col;
185 | dst->width = src->width;
186 | dst->height = src->height;
187 | unsigned char* dst_data = &dst->image_data[0];
188 | unsigned char* src_data = &src->image_data[0] + channel;
189 | int channelIncrement = (colortype == LCT_RGB) ? 3 : 4;
190 | for (row = 0; row < src->height; row++)
191 | {
192 | for (col = 0; col < src->width; col++)
193 | {
194 | *dst_data++ = *src_data;
195 | src_data += channelIncrement;
196 | }
197 | }
198 | }
199 |
200 | void convertToGrayscale(progimage_info* dst, progimage_info* src, LodePNGColorType colortype)
201 | {
202 | int row, col;
203 | dst->width = src->width;
204 | dst->height = src->height;
205 | unsigned char* dst_data = &dst->image_data[0];
206 | unsigned char* src_data = &src->image_data[0];
207 | int channelIncrement = (colortype == LCT_RGB) ? 3 : 4;
208 | for (row = 0; row < src->height; row++)
209 | {
210 | for (col = 0; col < src->width; col++)
211 | {
212 | // https://en.wikipedia.org/wiki/Grayscale#Luma_coding_in_video_systems
213 | *dst_data++ = (unsigned char)((0.2126f * ((float)src_data[0] / 255.0f) + 0.7152f * ((float)src_data[0] / 255.0f) + 0.0722f * ((float)src_data[0] / 255.0f)) * 255.0f + 0.5f);
214 | src_data += channelIncrement;
215 | }
216 | }
217 | }
218 |
219 |
220 | // to avoid defining boolean, etc., make this one return 1 if true, 0 if false
221 | int channelEqualsValue(progimage_info* src, int channel, int numChannels, unsigned char value, int ignoreGrayscale)
222 | {
223 | // look at all data in given channel - all equal to the given value?
224 | assert(numChannels > 0);
225 | assert(channel < numChannels);
226 | assert(ignoreGrayscale ? numChannels > 1 : 1);
227 | int row, col;
228 | unsigned char* src_data = &src->image_data[0] + channel;
229 | for (row = 0; row < src->height; row++)
230 | {
231 | for (col = 0; col < src->width; col++)
232 | {
233 | if (*src_data != value)
234 | {
235 | // do grayscale test?
236 | if (ignoreGrayscale) {
237 | if ((src_data[-channel] == src_data[1 - channel]) && (src_data[1 - channel] == src_data[2 - channel])) {
238 | // it's gray, so ignore it (could be a cutout background pixel)
239 | src_data += numChannels;
240 | continue;
241 | }
242 | }
243 | return 0;
244 | }
245 | src_data += numChannels;
246 | }
247 | }
248 | return 1;
249 | }
250 |
251 | void changeValueToValue(progimage_info* src, int channel, int numChannels, unsigned char value, unsigned char newValue)
252 | {
253 | // if value in channel is equal to input value, change it to the new value
254 | assert(numChannels > 0);
255 | assert(channel < numChannels);
256 | int row, col;
257 | unsigned char* src_data = &src->image_data[0] + channel;
258 | for (row = 0; row < src->height; row++)
259 | {
260 | for (col = 0; col < src->width; col++)
261 | {
262 | if (*src_data == value)
263 | {
264 | *src_data = newValue;
265 | }
266 | src_data += numChannels;
267 | }
268 | }
269 | }
270 |
271 |
272 |
273 |
--------------------------------------------------------------------------------
/rwpng.h:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (c) 2011, Eric Haines
3 | All rights reserved.
4 |
5 | Redistribution and use in source and binary forms, with or without
6 | modification, are permitted provided that the following conditions are met:
7 |
8 | * Redistributions of source code must retain the above copyright notice, this
9 | list of conditions and the following disclaimer.
10 |
11 | * Redistributions in binary form must reproduce the above copyright notice,
12 | this list of conditions and the following disclaimer in the documentation
13 | and/or other materials provided with the distribution.
14 |
15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
16 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
17 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
18 | ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
19 | LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
20 | CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
21 | SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
22 | INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
23 | CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
24 | ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
25 | THE POSSIBILITY OF SUCH DAMAGE.
26 | */
27 |
28 | #pragma once
29 |
30 | #include "lodepng.h"
31 |
32 | typedef struct _progimage_info {
33 | std::vector image_data; //the raw pixels
34 | int width = 0;
35 | int height = 0;
36 | } progimage_info;
37 |
38 | int readpng(progimage_info *mainprog_ptr, wchar_t *filename, LodePNGColorType colortype /*= LCT_RGBA*/);
39 | void readpng_cleanup(int free_image_data, progimage_info *mainprog_ptr);
40 | int readpngheader(progimage_info* im, wchar_t* filename, LodePNGColorType& colortype);
41 |
42 | int writepng(progimage_info *mainprog_ptr, int channels, wchar_t *filename);
43 | void writepng_cleanup(progimage_info *mainprog_ptr);
44 |
45 | progimage_info* allocateGrayscaleImage(progimage_info* source_ptr);
46 | progimage_info* allocateRGBImage(progimage_info* source_ptr);
47 | void copyOneChannel(progimage_info* dst, int channel, progimage_info* src, LodePNGColorType colortype);
48 | void convertToGrayscale(progimage_info* dst, progimage_info* src, LodePNGColorType colortype);
49 |
50 | int channelEqualsValue(progimage_info* src, int channel, int numChannels, unsigned char value, int ignoreGrayscale);
51 | void changeValueToValue(progimage_info* src, int channel, int numChannels, unsigned char value, unsigned char newValue);
--------------------------------------------------------------------------------
/stdafx.h:
--------------------------------------------------------------------------------
1 | // stdafx.h : include file for standard system include files,
2 | // or project specific include files that are used frequently, but
3 | // are changed infrequently
4 | //
5 |
6 | #pragma once
7 |
8 | #include "targetver.h"
9 |
10 | #include
11 | #include
12 |
13 |
14 |
15 | // TODO: reference additional headers your program requires here
16 |
--------------------------------------------------------------------------------
/stdio.cpp:
--------------------------------------------------------------------------------
1 | // Aseprite TGA Library
2 | // Copyright (C) 2020 Igara Studio S.A.
3 | //
4 | // This file is released under the terms of the MIT license.
5 | // Read LICENSE.txt for more information.
6 |
7 | #include "tga.h"
8 |
9 | namespace tga {
10 |
11 | StdioFileInterface::StdioFileInterface(FILE* file)
12 | : m_file(file)
13 | , m_ok(true)
14 | {
15 | }
16 |
17 | bool StdioFileInterface::ok() const
18 | {
19 | return m_ok;
20 | }
21 |
22 | size_t StdioFileInterface::tell()
23 | {
24 | return ftell(m_file);
25 | }
26 |
27 | void StdioFileInterface::seek(size_t absPos)
28 | {
29 | fseek(m_file, (long)absPos, SEEK_SET);
30 | }
31 |
32 | uint8_t StdioFileInterface::read8()
33 | {
34 | int value = fgetc(m_file);
35 | if (value != EOF)
36 | return (uint8_t)value;
37 |
38 | m_ok = false;
39 | return 0;
40 | }
41 |
42 | void StdioFileInterface::write8(uint8_t value)
43 | {
44 | fputc(value, m_file);
45 | }
46 |
47 | } // namespace tga
48 |
--------------------------------------------------------------------------------
/targetver.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | // Including SDKDDKVer.h defines the highest available Windows platform.
4 |
5 | // If you wish to build your application for a previous Windows platform, include WinSDKVer.h and
6 | // set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h.
7 |
8 | #include
9 |
--------------------------------------------------------------------------------
/test_files/Heightfields/grayscale_wood_floor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Heightfields/grayscale_wood_floor.png
--------------------------------------------------------------------------------
/test_files/Heightfields/ntp_heightfield.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Heightfields/ntp_heightfield.png
--------------------------------------------------------------------------------
/test_files/Heightfields/r_bump_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Heightfields/r_bump_map.png
--------------------------------------------------------------------------------
/test_files/Heightfields/squiggles.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Heightfields/squiggles.png
--------------------------------------------------------------------------------
/test_files/LICENSE:
--------------------------------------------------------------------------------
1 | Attribution-ShareAlike 4.0 International
2 |
3 | =======================================================================
4 |
5 | Creative Commons Corporation ("Creative Commons") is not a law firm and
6 | does not provide legal services or legal advice. Distribution of
7 | Creative Commons public licenses does not create a lawyer-client or
8 | other relationship. Creative Commons makes its licenses and related
9 | information available on an "as-is" basis. Creative Commons gives no
10 | warranties regarding its licenses, any material licensed under their
11 | terms and conditions, or any related information. Creative Commons
12 | disclaims all liability for damages resulting from their use to the
13 | fullest extent possible.
14 |
15 | Using Creative Commons Public Licenses
16 |
17 | Creative Commons public licenses provide a standard set of terms and
18 | conditions that creators and other rights holders may use to share
19 | original works of authorship and other material subject to copyright
20 | and certain other rights specified in the public license below. The
21 | following considerations are for informational purposes only, are not
22 | exhaustive, and do not form part of our licenses.
23 |
24 | Considerations for licensors: Our public licenses are
25 | intended for use by those authorized to give the public
26 | permission to use material in ways otherwise restricted by
27 | copyright and certain other rights. Our licenses are
28 | irrevocable. Licensors should read and understand the terms
29 | and conditions of the license they choose before applying it.
30 | Licensors should also secure all rights necessary before
31 | applying our licenses so that the public can reuse the
32 | material as expected. Licensors should clearly mark any
33 | material not subject to the license. This includes other CC-
34 | licensed material, or material used under an exception or
35 | limitation to copyright. More considerations for licensors:
36 | wiki.creativecommons.org/Considerations_for_licensors
37 |
38 | Considerations for the public: By using one of our public
39 | licenses, a licensor grants the public permission to use the
40 | licensed material under specified terms and conditions. If
41 | the licensor's permission is not necessary for any reason--for
42 | example, because of any applicable exception or limitation to
43 | copyright--then that use is not regulated by the license. Our
44 | licenses grant only permissions under copyright and certain
45 | other rights that a licensor has authority to grant. Use of
46 | the licensed material may still be restricted for other
47 | reasons, including because others have copyright or other
48 | rights in the material. A licensor may make special requests,
49 | such as asking that all changes be marked or described.
50 | Although not required by our licenses, you are encouraged to
51 | respect those requests where reasonable. More considerations
52 | for the public:
53 | wiki.creativecommons.org/Considerations_for_licensees
54 |
55 | =======================================================================
56 |
57 | Creative Commons Attribution-ShareAlike 4.0 International Public
58 | License
59 |
60 | By exercising the Licensed Rights (defined below), You accept and agree
61 | to be bound by the terms and conditions of this Creative Commons
62 | Attribution-ShareAlike 4.0 International Public License ("Public
63 | License"). To the extent this Public License may be interpreted as a
64 | contract, You are granted the Licensed Rights in consideration of Your
65 | acceptance of these terms and conditions, and the Licensor grants You
66 | such rights in consideration of benefits the Licensor receives from
67 | making the Licensed Material available under these terms and
68 | conditions.
69 |
70 |
71 | Section 1 -- Definitions.
72 |
73 | a. Adapted Material means material subject to Copyright and Similar
74 | Rights that is derived from or based upon the Licensed Material
75 | and in which the Licensed Material is translated, altered,
76 | arranged, transformed, or otherwise modified in a manner requiring
77 | permission under the Copyright and Similar Rights held by the
78 | Licensor. For purposes of this Public License, where the Licensed
79 | Material is a musical work, performance, or sound recording,
80 | Adapted Material is always produced where the Licensed Material is
81 | synched in timed relation with a moving image.
82 |
83 | b. Adapter's License means the license You apply to Your Copyright
84 | and Similar Rights in Your contributions to Adapted Material in
85 | accordance with the terms and conditions of this Public License.
86 |
87 | c. BY-SA Compatible License means a license listed at
88 | creativecommons.org/compatiblelicenses, approved by Creative
89 | Commons as essentially the equivalent of this Public License.
90 |
91 | d. Copyright and Similar Rights means copyright and/or similar rights
92 | closely related to copyright including, without limitation,
93 | performance, broadcast, sound recording, and Sui Generis Database
94 | Rights, without regard to how the rights are labeled or
95 | categorized. For purposes of this Public License, the rights
96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar
97 | Rights.
98 |
99 | e. Effective Technological Measures means those measures that, in the
100 | absence of proper authority, may not be circumvented under laws
101 | fulfilling obligations under Article 11 of the WIPO Copyright
102 | Treaty adopted on December 20, 1996, and/or similar international
103 | agreements.
104 |
105 | f. Exceptions and Limitations means fair use, fair dealing, and/or
106 | any other exception or limitation to Copyright and Similar Rights
107 | that applies to Your use of the Licensed Material.
108 |
109 | g. License Elements means the license attributes listed in the name
110 | of a Creative Commons Public License. The License Elements of this
111 | Public License are Attribution and ShareAlike.
112 |
113 | h. Licensed Material means the artistic or literary work, database,
114 | or other material to which the Licensor applied this Public
115 | License.
116 |
117 | i. Licensed Rights means the rights granted to You subject to the
118 | terms and conditions of this Public License, which are limited to
119 | all Copyright and Similar Rights that apply to Your use of the
120 | Licensed Material and that the Licensor has authority to license.
121 |
122 | j. Licensor means the individual(s) or entity(ies) granting rights
123 | under this Public License.
124 |
125 | k. Share means to provide material to the public by any means or
126 | process that requires permission under the Licensed Rights, such
127 | as reproduction, public display, public performance, distribution,
128 | dissemination, communication, or importation, and to make material
129 | available to the public including in ways that members of the
130 | public may access the material from a place and at a time
131 | individually chosen by them.
132 |
133 | l. Sui Generis Database Rights means rights other than copyright
134 | resulting from Directive 96/9/EC of the European Parliament and of
135 | the Council of 11 March 1996 on the legal protection of databases,
136 | as amended and/or succeeded, as well as other essentially
137 | equivalent rights anywhere in the world.
138 |
139 | m. You means the individual or entity exercising the Licensed Rights
140 | under this Public License. Your has a corresponding meaning.
141 |
142 |
143 | Section 2 -- Scope.
144 |
145 | a. License grant.
146 |
147 | 1. Subject to the terms and conditions of this Public License,
148 | the Licensor hereby grants You a worldwide, royalty-free,
149 | non-sublicensable, non-exclusive, irrevocable license to
150 | exercise the Licensed Rights in the Licensed Material to:
151 |
152 | a. reproduce and Share the Licensed Material, in whole or
153 | in part; and
154 |
155 | b. produce, reproduce, and Share Adapted Material.
156 |
157 | 2. Exceptions and Limitations. For the avoidance of doubt, where
158 | Exceptions and Limitations apply to Your use, this Public
159 | License does not apply, and You do not need to comply with
160 | its terms and conditions.
161 |
162 | 3. Term. The term of this Public License is specified in Section
163 | 6(a).
164 |
165 | 4. Media and formats; technical modifications allowed. The
166 | Licensor authorizes You to exercise the Licensed Rights in
167 | all media and formats whether now known or hereafter created,
168 | and to make technical modifications necessary to do so. The
169 | Licensor waives and/or agrees not to assert any right or
170 | authority to forbid You from making technical modifications
171 | necessary to exercise the Licensed Rights, including
172 | technical modifications necessary to circumvent Effective
173 | Technological Measures. For purposes of this Public License,
174 | simply making modifications authorized by this Section 2(a)
175 | (4) never produces Adapted Material.
176 |
177 | 5. Downstream recipients.
178 |
179 | a. Offer from the Licensor -- Licensed Material. Every
180 | recipient of the Licensed Material automatically
181 | receives an offer from the Licensor to exercise the
182 | Licensed Rights under the terms and conditions of this
183 | Public License.
184 |
185 | b. Additional offer from the Licensor -- Adapted Material.
186 | Every recipient of Adapted Material from You
187 | automatically receives an offer from the Licensor to
188 | exercise the Licensed Rights in the Adapted Material
189 | under the conditions of the Adapter's License You apply.
190 |
191 | c. No downstream restrictions. You may not offer or impose
192 | any additional or different terms or conditions on, or
193 | apply any Effective Technological Measures to, the
194 | Licensed Material if doing so restricts exercise of the
195 | Licensed Rights by any recipient of the Licensed
196 | Material.
197 |
198 | 6. No endorsement. Nothing in this Public License constitutes or
199 | may be construed as permission to assert or imply that You
200 | are, or that Your use of the Licensed Material is, connected
201 | with, or sponsored, endorsed, or granted official status by,
202 | the Licensor or others designated to receive attribution as
203 | provided in Section 3(a)(1)(A)(i).
204 |
205 | b. Other rights.
206 |
207 | 1. Moral rights, such as the right of integrity, are not
208 | licensed under this Public License, nor are publicity,
209 | privacy, and/or other similar personality rights; however, to
210 | the extent possible, the Licensor waives and/or agrees not to
211 | assert any such rights held by the Licensor to the limited
212 | extent necessary to allow You to exercise the Licensed
213 | Rights, but not otherwise.
214 |
215 | 2. Patent and trademark rights are not licensed under this
216 | Public License.
217 |
218 | 3. To the extent possible, the Licensor waives any right to
219 | collect royalties from You for the exercise of the Licensed
220 | Rights, whether directly or through a collecting society
221 | under any voluntary or waivable statutory or compulsory
222 | licensing scheme. In all other cases the Licensor expressly
223 | reserves any right to collect such royalties.
224 |
225 |
226 | Section 3 -- License Conditions.
227 |
228 | Your exercise of the Licensed Rights is expressly made subject to the
229 | following conditions.
230 |
231 | a. Attribution.
232 |
233 | 1. If You Share the Licensed Material (including in modified
234 | form), You must:
235 |
236 | a. retain the following if it is supplied by the Licensor
237 | with the Licensed Material:
238 |
239 | i. identification of the creator(s) of the Licensed
240 | Material and any others designated to receive
241 | attribution, in any reasonable manner requested by
242 | the Licensor (including by pseudonym if
243 | designated);
244 |
245 | ii. a copyright notice;
246 |
247 | iii. a notice that refers to this Public License;
248 |
249 | iv. a notice that refers to the disclaimer of
250 | warranties;
251 |
252 | v. a URI or hyperlink to the Licensed Material to the
253 | extent reasonably practicable;
254 |
255 | b. indicate if You modified the Licensed Material and
256 | retain an indication of any previous modifications; and
257 |
258 | c. indicate the Licensed Material is licensed under this
259 | Public License, and include the text of, or the URI or
260 | hyperlink to, this Public License.
261 |
262 | 2. You may satisfy the conditions in Section 3(a)(1) in any
263 | reasonable manner based on the medium, means, and context in
264 | which You Share the Licensed Material. For example, it may be
265 | reasonable to satisfy the conditions by providing a URI or
266 | hyperlink to a resource that includes the required
267 | information.
268 |
269 | 3. If requested by the Licensor, You must remove any of the
270 | information required by Section 3(a)(1)(A) to the extent
271 | reasonably practicable.
272 |
273 | b. ShareAlike.
274 |
275 | In addition to the conditions in Section 3(a), if You Share
276 | Adapted Material You produce, the following conditions also apply.
277 |
278 | 1. The Adapter's License You apply must be a Creative Commons
279 | license with the same License Elements, this version or
280 | later, or a BY-SA Compatible License.
281 |
282 | 2. You must include the text of, or the URI or hyperlink to, the
283 | Adapter's License You apply. You may satisfy this condition
284 | in any reasonable manner based on the medium, means, and
285 | context in which You Share Adapted Material.
286 |
287 | 3. You may not offer or impose any additional or different terms
288 | or conditions on, or apply any Effective Technological
289 | Measures to, Adapted Material that restrict exercise of the
290 | rights granted under the Adapter's License You apply.
291 |
292 |
293 | Section 4 -- Sui Generis Database Rights.
294 |
295 | Where the Licensed Rights include Sui Generis Database Rights that
296 | apply to Your use of the Licensed Material:
297 |
298 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right
299 | to extract, reuse, reproduce, and Share all or a substantial
300 | portion of the contents of the database;
301 |
302 | b. if You include all or a substantial portion of the database
303 | contents in a database in which You have Sui Generis Database
304 | Rights, then the database in which You have Sui Generis Database
305 | Rights (but not its individual contents) is Adapted Material,
306 |
307 | including for purposes of Section 3(b); and
308 | c. You must comply with the conditions in Section 3(a) if You Share
309 | all or a substantial portion of the contents of the database.
310 |
311 | For the avoidance of doubt, this Section 4 supplements and does not
312 | replace Your obligations under this Public License where the Licensed
313 | Rights include other Copyright and Similar Rights.
314 |
315 |
316 | Section 5 -- Disclaimer of Warranties and Limitation of Liability.
317 |
318 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE
319 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS
320 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF
321 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS,
322 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION,
323 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR
324 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS,
325 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT
326 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT
327 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU.
328 |
329 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE
330 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION,
331 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT,
332 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES,
333 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR
334 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN
335 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR
336 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR
337 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU.
338 |
339 | c. The disclaimer of warranties and limitation of liability provided
340 | above shall be interpreted in a manner that, to the extent
341 | possible, most closely approximates an absolute disclaimer and
342 | waiver of all liability.
343 |
344 |
345 | Section 6 -- Term and Termination.
346 |
347 | a. This Public License applies for the term of the Copyright and
348 | Similar Rights licensed here. However, if You fail to comply with
349 | this Public License, then Your rights under this Public License
350 | terminate automatically.
351 |
352 | b. Where Your right to use the Licensed Material has terminated under
353 | Section 6(a), it reinstates:
354 |
355 | 1. automatically as of the date the violation is cured, provided
356 | it is cured within 30 days of Your discovery of the
357 | violation; or
358 |
359 | 2. upon express reinstatement by the Licensor.
360 |
361 | For the avoidance of doubt, this Section 6(b) does not affect any
362 | right the Licensor may have to seek remedies for Your violations
363 | of this Public License.
364 |
365 | c. For the avoidance of doubt, the Licensor may also offer the
366 | Licensed Material under separate terms or conditions or stop
367 | distributing the Licensed Material at any time; however, doing so
368 | will not terminate this Public License.
369 |
370 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public
371 | License.
372 |
373 |
374 | Section 7 -- Other Terms and Conditions.
375 |
376 | a. The Licensor shall not be bound by any additional or different
377 | terms or conditions communicated by You unless expressly agreed.
378 |
379 | b. Any arrangements, understandings, or agreements regarding the
380 | Licensed Material not stated herein are separate from and
381 | independent of the terms and conditions of this Public License.
382 |
383 |
384 | Section 8 -- Interpretation.
385 |
386 | a. For the avoidance of doubt, this Public License does not, and
387 | shall not be interpreted to, reduce, limit, restrict, or impose
388 | conditions on any use of the Licensed Material that could lawfully
389 | be made without permission under this Public License.
390 |
391 | b. To the extent possible, if any provision of this Public License is
392 | deemed unenforceable, it shall be automatically reformed to the
393 | minimum extent necessary to make it enforceable. If the provision
394 | cannot be reformed, it shall be severed from this Public License
395 | without affecting the enforceability of the remaining terms and
396 | conditions.
397 |
398 | c. No term or condition of this Public License will be waived and no
399 | failure to comply consented to unless expressly agreed to by the
400 | Licensor.
401 |
402 | d. Nothing in this Public License constitutes or may be interpreted
403 | as a limitation upon, or waiver of, any privileges and immunities
404 | that apply to the Licensor or You, including from the legal
405 | processes of any jurisdiction or authority.
406 |
407 |
408 | =======================================================================
409 |
410 | Creative Commons is not a party to its public licenses.
411 | Notwithstanding, Creative Commons may elect to apply one of its public
412 | licenses to material it publishes and in those instances will be
413 | considered the “Licensor.” The text of the Creative Commons public
414 | licenses is dedicated to the public domain under the CC0 Public Domain
415 | Dedication. Except for the limited purpose of indicating that material
416 | is shared under a Creative Commons public license or as otherwise
417 | permitted by the Creative Commons policies published at
418 | creativecommons.org/policies, Creative Commons does not authorize the
419 | use of the trademark "Creative Commons" or any other trademark or logo
420 | of Creative Commons without its prior written consent including,
421 | without limitation, in connection with any unauthorized modifications
422 | to any of its public licenses or any other arrangements,
423 | understandings, or agreements concerning use of licensed material. For
424 | the avoidance of doubt, this paragraph does not form part of the public
425 | licenses.
426 |
427 | Creative Commons may be contacted at creativecommons.org.
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/black.tga:
--------------------------------------------------------------------------------
1 | d 2
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/cyan.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/NoneOfTheAbove/cyan.png
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/fade.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/NoneOfTheAbove/fade.png
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/gray.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/NoneOfTheAbove/gray.png
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/white.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/NoneOfTheAbove/white.tga
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/wood_floor.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/NoneOfTheAbove/wood_floor.png
--------------------------------------------------------------------------------
/test_files/NoneOfTheAbove/yellow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/NoneOfTheAbove/yellow.png
--------------------------------------------------------------------------------
/test_files/README.md:
--------------------------------------------------------------------------------
1 | # Normal Map test files
2 |
3 | Execute **run_test_suite.bat** on Windows to analyze and convert/clean-up the texture test files in this directory and subdirectories. Running this test file will create various separate output directories where the results are put.
4 |
5 | ## Directories
6 | * Standard - standard RGB -> XYZ, where Z is -1 to 1 range
7 | * XYtextures - RG -> XY, and Z is something else (like ambient occlusion, e.g., LabPBR)
8 | * ZZero - RGB -> XYZ, but Z goes from 0 to 1 instead of -1 to 1
9 | * Heightfields - heightfield test images
10 | * NoneOfTheAbove - other test images of various sorts
11 |
12 | Each directory's contents:
13 |
14 | ### Standard
15 |
16 | **lava_flow_n.png** - a file with almost no bumps, all nearly the same value. File is from the [jg-rtx](https://github.com/jasonjgardner/jg-rtx) resource pack for Minecraft, license [CC-NC-BY-SA](https://github.com/jasonjgardner/jg-rtx/blob/main/LICENSE).
17 |
18 | 
19 |
20 | **ntp_heightfield_gimp.png** - conversion of the **ntp_heightfield.png** heightfield to a normals texture with [GIMP](https://www.gimp.org/) using [these instructions](https://docs.gimp.org/en/gimp-filter-normal-map.html).
21 |
22 | 
23 |
24 | **ntp_heightfield_gimp_5scale.png** - conversion of the **ntp_heightfield.png** heightfield to a normals texture with [GIMP](https://www.gimp.org/), changing the scale to 5, using [these instructions](https://docs.gimp.org/en/gimp-filter-normal-map.html).
25 |
26 | 
27 |
28 | **ntp_heightfield_normalmap_online.png** - conversion of the **ntp_heightfield.png** heightfield to a normals texture with [Normalmap Online](https://cpetry.github.io/NormalMap-Online/), setting "Z-Range -1 to +1".
29 |
30 | 
31 |
32 | **ntp_resize_128.png** - the result file **ntp_heightfield_normalmap_online.png** resized to 128x128 using IrfanView. Doing a naive resize causes normals to no longer be normalized, see results in directory \_output_Heatmap.
33 |
34 | 
35 |
36 | **ntp_resize_512.png** - the result file **ntp_heightfield_normalmap_online.png** resized to 512x512 using IrfanView. Doing a naive resize causes normals to no longer be normalized, see results in directory \_output_Heatmap.
37 |
38 | 
39 |
40 | **r_normal_map.png** - letter R squiggles (see Heightfields directory) in standard form. Not quite correct, making it a good test. From the [USD Assets Working Group](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) Normals Texture Bias And Scale test, license CC-NC-BY-SA.
41 |
42 | 
43 |
44 | **r_normal_map_reversed_y.png** - letter R in standard form, DirectX orientation. Not quite correct, making it a good test. From the [USD Assets Working Group](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) Normals Texture Bias And Scale test, license CC-NC-BY-SA.
45 |
46 | 
47 |
48 | **squiggles_gimp.png** - squiggles (see Heightfields directory) converted with [GIMP](https://www.gimp.org/) using [these instructions](https://docs.gimp.org/en/gimp-filter-normal-map.html). GIMP evidently uses the DirectX style for output by default, though has a "Flip Y" and other options to adjust the style.
49 |
50 | 
51 |
52 | **squiggles_normalmap_online_zneg.png** - converted using [NormalMap Online](https://cpetry.github.io/NormalMap-Online/), with the "Z Range -1 to +1" box checked.
53 |
54 | 
55 |
56 | **squiggles_ntp_quick.png** - converted by an earlier version of the NormalTextureProcessor.exe's heightfield converter, but without adjusting for roundtripping. Some pixels have one-level differences with **squiggles_ntp_roundtrip.png**. Use a "diff" program such as Beyond Compare to see the differences between the two. Note if you read this file in and convert it, the result should be the same as **squiggles_ntp_roundtrip.png**.
57 |
58 | 
59 |
60 | **squiggles_ntp_roundtrip.png** - converted by NormalTextureProcessor.exe's heightfield converter using roundtripping. If you try to clean this file, the output file should be identical, since all the texels are perfectly roundtrippable (see the **Algorithms** section on the main page).
61 |
62 | 
63 |
64 | **targa_r_normal_map.tga** - (no image shown) same as r_normal_map.png**, the letter R in standard form. Test that Targa input works. Converted from the [USD Assets Working Group](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) Normals Texture Bias And Scale test, license CC-NC-BY-SA.
65 |
66 | ### XYtextures
67 |
68 | **acacia_door_bottom_n.png** - the blue channel is ambient occlusion, [LabPBR format](https://shaderlabs.org/wiki/LabPBR_Material_Standard). File is from the [jg-rtx](https://github.com/jasonjgardner/jg-rtx) resource pack for Minecraft, license [CC-NC-BY-SA](https://github.com/jasonjgardner/jg-rtx/blob/main/LICENSE).
69 |
70 | 
71 |
72 | ### ZZero
73 |
74 | **r_normal_map_reversed_x_0_bias_z.png** - letter R in Z-Zero form, and the X axis is reversed (non-standard, just meant as a test). Not quite correct, making it a good test. From the [USD Assets Working Group](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) Normals Texture Bias And Scale test, license CC-NC-BY-SA.
75 |
76 | 
77 |
78 | **squiggles_normalmap_online_zzero.png** - converted using [NormalMap Online](https://cpetry.github.io/NormalMap-Online/).
79 |
80 | 
81 |
82 | ### Heightfields
83 |
84 | **grayscale_wood_floor.png** - grayscale wood floor sample, non-tiling.
85 |
86 | 
87 |
88 | **ntp_heightfield.png** - text and brushes heightfield, giving a wide variety of slopes
89 |
90 | 
91 |
92 | **r_bump_map.png** - grayscale letter R. From the [USD Assets Working Group](https://github.com/usd-wg/assets/tree/main/test_assets/NormalsTextureBiasAndScale) Normals Texture Bias And Scale test, license CC-NC-BY-SA.
93 |
94 | 
95 |
96 | **squiggles.png** - brushstrokes of similar slopes. Note there are strokes that touch the top edge, causing conversion glitches due to wrap-around; using '-hborder' would be a more sensible conversion, which is done as a separate test.
97 |
98 | 
99 |
100 | ### NoneOfTheAbove
101 |
102 | **black.tga** - (no image shown) all black, as a test. Non-power-of-two non-square size.
103 |
104 | **cyan.png** - solid cyan, as a test.
105 |
106 | 
107 |
108 | **fade.png** - fade from blue to green to red, as a test.
109 |
110 | 
111 |
112 | **gray.png** - a neutral gray, as a test. Non-power-of-two non-square size.
113 |
114 | 
115 |
116 | **white.tga** - (no image shown) all white, as a test.
117 |
118 | **wood_floor.png** - colored wood floor sample, non-tiling. Currently this file gets misinterpreted as being an XY-only texture, because the Z's are clearly not used properly and the X and Y values, due to the coloration, mostly form vectors that have a length less than 1.0.
119 |
120 | 
121 |
122 | **yellow.png** - solid yellow, as a test.
123 |
124 | 
125 |
126 | ## License
127 |
128 | For images, the license is [CC-NC-BY-SA](https://github.com/erich666/NormalTextureProcessor/test_files/LICENSE) and created by Eric Haines, unless noted otherwise above.
--------------------------------------------------------------------------------
/test_files/Standard/lava_flow_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/lava_flow_n.png
--------------------------------------------------------------------------------
/test_files/Standard/ntp_heightfield_gimp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/ntp_heightfield_gimp.png
--------------------------------------------------------------------------------
/test_files/Standard/ntp_heightfield_gimp_5scale.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/ntp_heightfield_gimp_5scale.png
--------------------------------------------------------------------------------
/test_files/Standard/ntp_heightfield_normalmap_online.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/ntp_heightfield_normalmap_online.png
--------------------------------------------------------------------------------
/test_files/Standard/ntp_resize_128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/ntp_resize_128.png
--------------------------------------------------------------------------------
/test_files/Standard/ntp_resize_512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/ntp_resize_512.png
--------------------------------------------------------------------------------
/test_files/Standard/r_normal_map.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/r_normal_map.png
--------------------------------------------------------------------------------
/test_files/Standard/r_normal_map_reversed_y.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/r_normal_map_reversed_y.png
--------------------------------------------------------------------------------
/test_files/Standard/squiggles_gimp.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/squiggles_gimp.png
--------------------------------------------------------------------------------
/test_files/Standard/squiggles_normalmap_online_zneg.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/squiggles_normalmap_online_zneg.png
--------------------------------------------------------------------------------
/test_files/Standard/squiggles_ntp_quick.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/squiggles_ntp_quick.png
--------------------------------------------------------------------------------
/test_files/Standard/squiggles_ntp_roundtrip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/squiggles_ntp_roundtrip.png
--------------------------------------------------------------------------------
/test_files/Standard/targa_r_normal_map.tga:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/Standard/targa_r_normal_map.tga
--------------------------------------------------------------------------------
/test_files/XYtextures/acacia_door_bottom_n.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/XYtextures/acacia_door_bottom_n.png
--------------------------------------------------------------------------------
/test_files/ZZero/r_normal_map_reversed_x_0_bias_z.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/ZZero/r_normal_map_reversed_x_0_bias_z.png
--------------------------------------------------------------------------------
/test_files/ZZero/squiggles_normalmap_online_zzero.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/erich666/NormalTextureProcessor/6c4ec4c2315393b9e73f74ee7404369528538a39/test_files/ZZero/squiggles_normalmap_online_zzero.png
--------------------------------------------------------------------------------
/test_files/run_test_suite.bat:
--------------------------------------------------------------------------------
1 | @rem run_test_suite.bat - run to test the NormalTextureProcessor. Assumes the release version of the program has been compiled and is in the normal place.
2 | set NTP_EXE="..\x64\Release\NormalTextureProcessor.exe"
3 |
4 | @rem run everything, as a start
5 | @rem we could force everything to be considered a heightfield for this next test by using -ihf, but it turns out these all pass anyway
6 | %NTP_EXE% -idir Standard -odir _output_Standard -oall -a -v
7 | %NTP_EXE% -idir ZZero -odir _output_ZZero -oall -a -v
8 | %NTP_EXE% -idir XYtextures -odir _output_XYtextures -oall -a -v
9 | %NTP_EXE% -idir Heightfields -odir _output_Heightfields -oall -a -v
10 | @rem how are various non-normal, non-bump files interpreted?
11 | %NTP_EXE% -idir NoneOfTheAbove -odir _output_NoneOfTheAbove -oall -a -v
12 |
13 | @rem Other tests follow, mostly unit tests to exercise and demonstrate the various options
14 |
15 | @rem the 0 to 1 file produced by NormalMap Online is so funky we have to force it to be read in as Z-zero, using -izzero. Also output as -ozzero, making this a cleanup sort of pass
16 | %NTP_EXE% -idir ZZero -odir _output_ZZero_stay_zzero -oclean -a -v -izzero -ozzero squiggles_normalmap_online_zzero.png
17 |
18 | @rem another way is to set the tolerance percentage higher, '-etol 8', which increases the tolerance for bad normals (and other bad data), so letting this one be classified as Z-zero
19 | %NTP_EXE% -idir ZZero -odir _output_ZZero_find_zzero -oclean -a -v -etol 8 squiggles_normalmap_online_zzero.png
20 |
21 | @rem make the wood bumpmap have more extreme bumps and have borders instead of being tiling (repeating)
22 | %NTP_EXE% -idir Heightfields -odir _output_Heightfields_scale -oall -a -v -hfs 15 grayscale_wood_floor.png -hborder
23 |
24 | @rem convert the squiggles heightfield to have a border, which makes more sense since it's not meant to be tiling
25 | %NTP_EXE% -idir Heightfields -odir _output_Heightfields_border -oall -a -v squiggles.png -hborder
26 |
27 | @rem force colored wood floor to be considered a heightfield
28 | %NTP_EXE% -idir NoneOfTheAbove -odir _output_NoneOfTheAbove_force -oall -a -v -ihf wood_floor.png
29 |
30 | @rem GIMP gives DirectX-style by default. Convert to OpenGL-style, and force to be standard Z, just to test the -izneg flag
31 | %NTP_EXE% -idir Standard -odir _output_Standard_OpenGL -oall -a -v -izneg -idx squiggles_gimp.png
32 |
33 | @rem convert the r_normal_map.png OpenGL-style file to DirectX style. Note there is no '-iogl' input option needed, since OpenGL format is the default.
34 | %NTP_EXE% -idir Standard -odir _output_Standard_DirectX -oall -a -v -izneg -odx r_normal_map.png
35 |
36 | @rem export to CSV, showing only errors
37 | %NTP_EXE% -idir Standard -odir _output_CSV -a -v squiggles_normalmap_online_zneg.png -csve -izneg
38 | %NTP_EXE% -idir Standard -odir _output_CSV -a -v ntp_heightfield_normalmap_online.png -csve -izneg
39 |
40 | @rem export to CSV, dumping all pixels
41 | %NTP_EXE% -idir NoneOfTheAbove -odir _output_CSV -a -v fade.png -csv -ixy
42 |
43 | @rem make heatmaps, tell program that files are of a given format
44 | %NTP_EXE% -idir Standard -odir _output_Heatmap -oall -izneg -a -v -oheatmap
45 | %NTP_EXE% -idir XYtextures -odir _output_Heatmap -oall -ixy -a -v -oheatmap
46 | %NTP_EXE% -idir ZZero -odir _output_Heatmap -oall -izzero -a -v -oheatmap
47 |
48 | @rem TODO: Flag currently not tested here, due to a lack of bad test data: -allownegz -etolxy
49 |
--------------------------------------------------------------------------------
/tga.h:
--------------------------------------------------------------------------------
1 | // Aseprite TGA Library
2 | // Copyright (C) 2020 Igara Studio S.A.
3 | //
4 | // This file is released under the terms of the MIT license.
5 | // Read LICENSE.txt for more information.
6 |
7 | #ifndef TGA_TGA_H_INCLUDED
8 | #define TGA_TGA_H_INCLUDED
9 | #pragma once
10 |
11 | #include
12 |
13 | #include
14 | #include
15 | #include
16 |
17 | namespace tga {
18 |
19 | enum ImageType {
20 | NoImage = 0,
21 | UncompressedIndexed = 1,
22 | UncompressedRgb = 2,
23 | UncompressedGray = 3,
24 | RleIndexed = 9,
25 | RleRgb = 10,
26 | RleGray = 11,
27 | };
28 |
29 | typedef uint32_t color_t;
30 |
31 | const color_t color_r_shift = 0;
32 | const color_t color_g_shift = 8;
33 | const color_t color_b_shift = 16;
34 | const color_t color_a_shift = 24;
35 | const color_t color_r_mask = 0x000000ff;
36 | const color_t color_g_mask = 0x0000ff00;
37 | const color_t color_b_mask = 0x00ff0000;
38 | const color_t color_rgb_mask = 0x00ffffff;
39 | const color_t color_a_mask = 0xff000000;
40 |
41 | inline uint8_t getr(color_t c) { return (c >> color_r_shift) & 0xff; }
42 | inline uint8_t getg(color_t c) { return (c >> color_g_shift) & 0xff; }
43 | inline uint8_t getb(color_t c) { return (c >> color_b_shift) & 0xff; }
44 | inline uint8_t geta(color_t c) { return (c >> color_a_shift) & 0xff; }
45 | inline color_t rgba(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255) {
46 | return ((r << color_r_shift) |
47 | (g << color_g_shift) |
48 | (b << color_b_shift) |
49 | (a << color_a_shift));
50 | }
51 |
52 | class Colormap {
53 | public:
54 | Colormap() { }
55 | Colormap(const int n) : m_color(n) { }
56 |
57 | int size() const { return int(m_color.size()); }
58 |
59 | const color_t& operator[](int i) const {
60 | return m_color[i];
61 | }
62 |
63 | color_t& operator[](int i) {
64 | return m_color[i];
65 | }
66 |
67 | bool operator==(const Colormap& o) const {
68 | for (int i=0; i m_color;
81 | };
82 |
83 | struct Image {
84 | uint8_t* pixels;
85 | uint32_t bytesPerPixel;
86 | uint32_t rowstride;
87 | };
88 |
89 | struct Header {
90 | uint8_t idLength;
91 | uint8_t colormapType;
92 | uint8_t imageType;
93 | uint16_t colormapOrigin;
94 | uint16_t colormapLength;
95 | uint8_t colormapDepth;
96 | uint16_t xOrigin;
97 | uint16_t yOrigin;
98 | uint16_t width;
99 | uint16_t height;
100 | uint8_t bitsPerPixel;
101 | uint8_t imageDescriptor;
102 | std::string imageId;
103 | Colormap colormap;
104 |
105 | bool leftToRight() const { return !(imageDescriptor & 0x10); }
106 | bool topToBottom() const { return (imageDescriptor & 0x20); }
107 |
108 | bool hasColormap() const {
109 | return (colormapLength > 0);
110 | }
111 |
112 | bool isRgb() const {
113 | return (imageType == UncompressedRgb ||
114 | imageType == RleRgb);
115 | }
116 |
117 | bool isIndexed() const {
118 | return (imageType == UncompressedIndexed ||
119 | imageType == RleIndexed);
120 | }
121 |
122 | bool isGray() const {
123 | return (imageType == UncompressedGray ||
124 | imageType == RleGray);
125 | }
126 |
127 | bool isUncompressed() const {
128 | return (imageType == UncompressedIndexed ||
129 | imageType == UncompressedRgb ||
130 | imageType == UncompressedGray);
131 | }
132 |
133 | bool isRle() const {
134 | return (imageType == RleIndexed ||
135 | imageType == RleRgb ||
136 | imageType == RleGray);
137 | }
138 |
139 | bool validColormapType() const {
140 | return
141 | // Indexed with palette
142 | (isIndexed() && bitsPerPixel == 8 && colormapType == 1) ||
143 | // Grayscale without palette
144 | (isGray() && bitsPerPixel == 8 && colormapType == 0) ||
145 | // Non-indexed without palette
146 | (bitsPerPixel > 8 && colormapType == 0);
147 | }
148 |
149 | bool valid() const {
150 | switch (imageType) {
151 | case UncompressedIndexed:
152 | case RleIndexed:
153 | return (bitsPerPixel == 8);
154 | case UncompressedRgb:
155 | case RleRgb:
156 | return (bitsPerPixel == 15 ||
157 | bitsPerPixel == 16 ||
158 | bitsPerPixel == 24 ||
159 | bitsPerPixel == 32);
160 | case UncompressedGray:
161 | case RleGray:
162 | return (bitsPerPixel == 8);
163 | }
164 | return false;
165 | }
166 |
167 | // Returns the number of bytes per pixel needed in an image
168 | // created with this Header information.
169 | int bytesPerPixel() const {
170 | if (isRgb())
171 | return 4;
172 | else
173 | return 1;
174 | }
175 |
176 | };
177 |
178 | namespace details {
179 |
180 | class ImageIterator {
181 | public:
182 | ImageIterator();
183 | ImageIterator(const Header& header, Image& image);
184 |
185 | // Put a pixel value into the image and advance the iterator.
186 | template
187 | bool putPixel(const T value) {
188 | *((T*)m_ptr) = value;
189 | return advance();
190 | }
191 |
192 | // Get one pixel from the image and advance the iterator.
193 | template
194 | T getPixel() {
195 | T value = *((T*)m_ptr);
196 | advance();
197 | return value;
198 | }
199 |
200 | public:
201 | bool advance();
202 | void calcPtr();
203 |
204 | Image* m_image;
205 | int m_x, m_y;
206 | int m_w, m_h;
207 | int m_dx, m_dy;
208 | uint8_t* m_ptr;
209 | };
210 |
211 | } // namespace details
212 |
213 | class FileInterface {
214 | public:
215 | virtual ~FileInterface() { }
216 |
217 | // Returns true if we can read/write bytes from/into the file
218 | virtual bool ok() const = 0;
219 |
220 | // Current position in the file
221 | virtual size_t tell() = 0;
222 |
223 | // Jump to the given position in the file
224 | virtual void seek(size_t absPos) = 0;
225 |
226 | // Returns the next byte in the file or 0 if ok() = false
227 | virtual uint8_t read8() = 0;
228 |
229 | // Writes one byte in the file (or do nothing if ok() = false)
230 | virtual void write8(uint8_t value) = 0;
231 | };
232 |
233 | class StdioFileInterface : public tga::FileInterface {
234 | public:
235 | StdioFileInterface(FILE* file);
236 | bool ok() const override;
237 | size_t tell() override;
238 | void seek(size_t absPos) override;
239 | uint8_t read8() override;
240 | void write8(uint8_t value) override;
241 |
242 | private:
243 | FILE* m_file;
244 | bool m_ok;
245 | };
246 |
247 | class Delegate {
248 | public:
249 | virtual ~Delegate() {}
250 | // Must true if we should continue the decoding process.
251 | virtual bool notifyProgress(double progress) = 0;
252 | };
253 |
254 | class Decoder {
255 | public:
256 | Decoder(FileInterface* file);
257 |
258 | bool hasAlpha() const { return m_hasAlpha; }
259 |
260 | // Reads the header + colormap (if the file has a
261 | // colormap). Returns true if the header is valid.
262 | bool readHeader(Header& header);
263 |
264 | // Reads the image.
265 | bool readImage(const Header& header,
266 | Image& image,
267 | Delegate* delegate = nullptr);
268 |
269 | // Fixes alpha channel for images with invalid alpha values (this
270 | // is optional, in case you want to preserve the original data
271 | // from the file, don't use it).
272 | void postProcessImage(const Header& header,
273 | Image& image);
274 |
275 | private:
276 | void readColormap(Header& header);
277 |
278 | template
279 | bool readUncompressedData(const int w, uint32_t (Decoder::*readPixel)());
280 |
281 | template
282 | bool readRleData(const int w, uint32_t (Decoder::*readPixel)());
283 |
284 | uint32_t read8();
285 | uint32_t read16();
286 | uint32_t read32();
287 | uint32_t read32AsRgb();
288 | uint32_t read24AsRgb();
289 | uint32_t read16AsRgb();
290 |
291 | FileInterface* m_file;
292 | bool m_hasAlpha = false;
293 | details::ImageIterator m_iterator;
294 | };
295 |
296 | class Encoder {
297 | public:
298 | Encoder(FileInterface* file);
299 |
300 | // Writes the header + colormap
301 | void writeHeader(const Header& header);
302 | void writeImage(const Header& header,
303 | const Image& image,
304 | Delegate* delegate = nullptr);
305 | void writeFooter();
306 |
307 | private:
308 | template
309 | void writeRleScanline(const int w, const Image& image,
310 | int y, void (Encoder::*writePixel)(color_t));
311 |
312 | template
313 | void countRepeatedPixels(const int w, const Image& image,
314 | int x0, int y, int& offset, int& count);
315 |
316 | void write8(uint32_t value);
317 | void write16(uint32_t value);
318 | void write32(uint32_t value);
319 | void write16Rgb(uint32_t c);
320 | void write24Rgb(uint32_t c);
321 | void write32Rgb(uint32_t c);
322 |
323 | FileInterface* m_file;
324 | bool m_hasAlpha = false;
325 | details::ImageIterator m_iterator;
326 | };
327 |
328 | } // namespace tga
329 |
330 | #endif
331 |
--------------------------------------------------------------------------------
/tga_decoder.cpp:
--------------------------------------------------------------------------------
1 | // Aseprite TGA Library
2 | // Copyright (C) 2020 Igara Studio S.A.
3 | //
4 | // This file is released under the terms of the MIT license.
5 | // Read LICENSE.txt for more information.
6 |
7 | #include "tga.h"
8 |
9 | #include
10 |
11 | namespace tga {
12 |
13 | static inline int scale_5bits_to_8bits(int v) {
14 | assert(v >= 0 && v < 32);
15 | return (v << 3) | (v >> 2);
16 | }
17 |
18 | Decoder::Decoder(FileInterface* file)
19 | : m_file(file)
20 | {
21 | }
22 |
23 | bool Decoder::readHeader(Header& header)
24 | {
25 | header.idLength = read8();
26 | header.colormapType = read8();
27 | header.imageType = read8();
28 | header.colormapOrigin = read16();
29 | header.colormapLength = read16();
30 | header.colormapDepth = read8();
31 | header.xOrigin = read16();
32 | header.yOrigin = read16();
33 | header.width = read16();
34 | header.height = read16();
35 | header.bitsPerPixel = read8();
36 | header.imageDescriptor = read8();
37 |
38 | // Invalid image size
39 | if (header.width == 0 ||
40 | header.height == 0)
41 | return false;
42 |
43 | // Skip ID string (idLength bytes)
44 | if (header.idLength > 0) {
45 | uint8_t i = header.idLength;
46 | while (i--) {
47 | uint8_t chr = m_file->read8();
48 | header.imageId.push_back(chr);
49 | }
50 | }
51 |
52 | #if 0
53 | // In the best case the "alphaBits" should be valid, but there are
54 | // invalid TGA files out there which don't indicate the
55 | // "alphaBits" correctly, so they could be 0 and use the alpha
56 | // channel anyway on each pixel.
57 | int alphaBits = (header.imageDescriptor & 15);
58 | m_hasAlpha =
59 | (header.bitsPerPixel == 32 && alphaBits == 8) ||
60 | (header.bitsPerPixel == 16 && alphaBits == 1);
61 | #else
62 | // So to detect if a 32bpp or 16bpp TGA image has alpha, we'll use
63 | // the "alpha histogram" in postProcessImage() to check if there are
64 | // different alpha values. If there is only one alpha value (all 0
65 | // or all 255), we create an opaque image anyway. The only exception
66 | // to this rule is when all pixels are black and transparent
67 | // (RGBA=0), that is the only case when an image is fully
68 | // transparent.
69 | //
70 | // Note: This same heuristic is used in apps like macOS Preview:
71 | // https://twitter.com/davidcapello/status/1242803110868893697
72 | m_hasAlpha =
73 | (header.bitsPerPixel == 32) ||
74 | (header.bitsPerPixel == 16);
75 | #endif
76 |
77 | // Read colormap
78 | if (header.colormapType == 1)
79 | readColormap(header);
80 |
81 | return (header.validColormapType() &&
82 | header.valid());
83 | }
84 |
85 | void Decoder::readColormap(Header& header)
86 | {
87 | header.colormap = Colormap(header.colormapLength);
88 |
89 | for (int i=0; i> 10) & 0x1F),
97 | scale_5bits_to_8bits((c >> 5) & 0x1F),
98 | scale_5bits_to_8bits(c & 0x1F));
99 | break;
100 | }
101 |
102 | case 24:
103 | case 32: {
104 | const int b = read8();
105 | const int g = read8();
106 | const int r = read8();
107 | int a;
108 | if (header.colormapDepth == 32)
109 | a = read8();
110 | else
111 | a = 255;
112 | header.colormap[i] = rgba(r, g, b, a);
113 | break;
114 | }
115 | }
116 | }
117 | }
118 |
119 | bool Decoder::readImage(const Header& header,
120 | Image& image,
121 | Delegate* delegate)
122 | {
123 | // Bit 4 means right-to-left, else left-to-right
124 | // Bit 5 means top-to-bottom, else bottom-to-top
125 | m_iterator = details::ImageIterator(header, image);
126 |
127 | for (int y=0; y(header.width, &Decoder::read8))
133 | return true;
134 | break;
135 |
136 | case UncompressedRgb:
137 | switch (header.bitsPerPixel) {
138 | case 15:
139 | case 16:
140 | if (readUncompressedData(header.width, &Decoder::read16AsRgb))
141 | return true;
142 | break;
143 | case 24:
144 | if (readUncompressedData(header.width, &Decoder::read24AsRgb))
145 | return true;
146 | break;
147 | case 32:
148 | if (readUncompressedData(header.width, &Decoder::read32AsRgb))
149 | return true;
150 | break;
151 | default:
152 | assert(false);
153 | break;
154 | }
155 | break;
156 |
157 | case UncompressedGray:
158 | assert(header.bitsPerPixel == 8);
159 | if (readUncompressedData(header.width, &Decoder::read8))
160 | return true;
161 | break;
162 |
163 | case RleIndexed:
164 | assert(header.bitsPerPixel == 8);
165 | if (readRleData(header.width, &Decoder::read8))
166 | return true;
167 | break;
168 |
169 | case RleRgb:
170 | switch (header.bitsPerPixel) {
171 | case 15:
172 | case 16:
173 | if (readRleData(header.width, &Decoder::read16AsRgb))
174 | return true;
175 | break;
176 | case 24:
177 | if (readRleData(header.width, &Decoder::read24AsRgb))
178 | return true;
179 | break;
180 | case 32:
181 | if (readRleData(header.width, &Decoder::read32AsRgb))
182 | return true;
183 | break;
184 | default:
185 | assert(false);
186 | break;
187 | }
188 | break;
189 |
190 | case RleGray:
191 | assert(header.bitsPerPixel == 8);
192 | if (readRleData(header.width, &Decoder::read8))
193 | return true;
194 | break;
195 | }
196 |
197 | if (delegate &&
198 | !delegate->notifyProgress(float(y) / float(header.height))) {
199 | break;
200 | }
201 | }
202 |
203 | return true;
204 | }
205 |
206 | void Decoder::postProcessImage(const Header& header,
207 | Image& image)
208 | {
209 | // The post-processing is only for RGB images with possible invalid
210 | // alpha information.
211 | if (!header.isRgb() || !m_hasAlpha)
212 | return;
213 |
214 | bool transparentImage = true;
215 | bool blackImage = true;
216 |
217 | for (int y=0; y
251 | bool Decoder::readUncompressedData(const int w, color_t (Decoder::*readPixel)())
252 | {
253 | for (int x=0; x((this->*readPixel)()))
255 | return true;
256 | }
257 | return false;
258 | }
259 |
260 | // In the best case (TGA 2.0 spec) this should read just one
261 | // scanline, but in old TGA versions (1.0) it was possible to save
262 | // several scanlines with the same RLE data.
263 | //
264 | // Returns true when are are done.
265 | template
266 | bool Decoder::readRleData(const int w, color_t (Decoder::*readPixel)())
267 | {
268 | for (int x=0; xok(); ) {
269 | int c = read8();
270 | if (c & 0x80) {
271 | c = (c & 0x7f) + 1;
272 | x += c;
273 | const T pixel = (this->*readPixel)();
274 | while (c-- > 0)
275 | if (m_iterator.putPixel(pixel))
276 | return true;
277 | }
278 | else {
279 | ++c;
280 | x += c;
281 | while (c-- > 0) {
282 | if (m_iterator.putPixel((this->*readPixel)()))
283 | return true;
284 | }
285 | }
286 | }
287 | return false;
288 | }
289 |
290 | uint32_t Decoder::read8()
291 | {
292 | return m_file->read8();
293 | }
294 |
295 | // Reads a WORD (16 bits) using in little-endian byte ordering.
296 | uint32_t Decoder::read16()
297 | {
298 | uint8_t b1 = m_file->read8();
299 | uint8_t b2 = m_file->read8();
300 |
301 | if (m_file->ok()) {
302 | return ((b2 << 8) | b1); // Little endian
303 | }
304 | else
305 | return 0;
306 | }
307 |
308 | // Reads a DWORD (32 bits) using in little-endian byte ordering.
309 | uint32_t Decoder::read32()
310 | {
311 | const uint8_t b1 = m_file->read8();
312 | const uint8_t b2 = m_file->read8();
313 | const uint8_t b3 = m_file->read8();
314 | const uint8_t b4 = m_file->read8();
315 |
316 | if (m_file->ok()) {
317 | // Little endian
318 | return ((b4 << 24) | (b3 << 16) | (b2 << 8) | b1);
319 | }
320 | else
321 | return 0;
322 | }
323 |
324 | uint32_t Decoder::read32AsRgb()
325 | {
326 | const uint8_t b = read8();
327 | const uint8_t g = read8();
328 | const uint8_t r = read8();
329 | uint8_t a = read8();
330 | if (!m_hasAlpha)
331 | a = 255;
332 | return rgba(r, g, b, a);
333 | }
334 |
335 | uint32_t Decoder::read24AsRgb()
336 | {
337 | const uint8_t b = read8();
338 | const uint8_t g = read8();
339 | const uint8_t r = read8();
340 | return rgba(r, g, b, 255);
341 | }
342 |
343 | uint32_t Decoder::read16AsRgb()
344 | {
345 | const uint16_t v = read16();
346 | uint8_t a = 255;
347 | if (m_hasAlpha) {
348 | if ((v & 0x8000) == 0) // Transparent bit
349 | a = 0;
350 | }
351 | return rgba(scale_5bits_to_8bits((v >> 10) & 0x1F),
352 | scale_5bits_to_8bits((v >> 5) & 0x1F),
353 | scale_5bits_to_8bits(v & 0x1F),
354 | a);
355 | }
356 |
357 | } // namespace tga
358 |
--------------------------------------------------------------------------------