Skip to content

Commit 06c6a5a

Browse files
committed
image_texture rewrite
- Old constructor took image data plus dimensions, new constructor just takes the image filename and does all loading. - Added error message when the image file is not found. - Fixed numerous pixel lookup off-by-a-smidge bugs. - Increased bullet-proofing of pixel lookup. - Optimized code to avoid recalculating expressions many times. - Simplified the calling sites. - STB image load now specifies exactly three components per pixel (RGB) - Added explicit member initialization for the default constructor. I'd love to delete the default constructor altogether, but this triggers warnings on some build environments. Resolves #434
1 parent c17f26d commit 06c6a5a

File tree

4 files changed

+86
-53
lines changed

4 files changed

+86
-53
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ Change Log -- Ray Tracing in One Weekend
1717
- Fix: Improve image size and aspect ratio calculation to make size changes easier
1818
- Fix: Added `t` parameter back into `hit_record` at correct place
1919

20+
### _The Next Week_
21+
- Change: Large rewrite of the `image_texture` class. Now handles image loading too. (#434)
22+
2023

2124
----------------------------------------------------------------------------------------------------
2225
# v3.0.1 (2020-03-31)

books/RayTracingTheNextWeek.html

Lines changed: 42 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1741,41 +1741,63 @@
17411741

17421742
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
17431743
#include "rtweekend.h"
1744+
#include "rtw_stb_image.h"
1745+
1746+
#include <iostream>
17441747

17451748
class image_texture : public texture {
17461749
public:
1747-
image_texture() {}
1748-
image_texture(unsigned char *pixels, int A, int B)
1749-
: data(pixels), nx(A), ny(B) {}
1750+
const static int bytes_per_pixel = 3;
1751+
1752+
image_texture()
1753+
: data(nullptr), width(0), height(0), bytes_per_scanline(0) {}
1754+
1755+
image_texture(const char* filename) {
1756+
auto components_per_pixel = bytes_per_pixel;
1757+
1758+
data = stbi_load(
1759+
filename, &width, &height, &components_per_pixel, components_per_pixel);
1760+
1761+
if (!data) {
1762+
std::cerr << "ERROR: Could not load texture image file '" << filename << "'.\n";
1763+
width = height = 0;
1764+
}
1765+
1766+
bytes_per_scanline = bytes_per_pixel * width;
1767+
}
17501768

17511769
~image_texture() {
17521770
delete data;
17531771
}
17541772

1755-
virtual color value(double u, double v, const point3& p) const {
1756-
// If we have no texture data, then always emit cyan (as a debugging aid).
1773+
virtual color value(double u, double v, const vec3& p) const {
1774+
// If we have no texture data, then return solid cyan as a debugging aid.
17571775
if (data == nullptr)
17581776
return color(0,1,1);
17591777

1760-
auto i = static_cast<int>(( u)*nx);
1761-
auto j = static_cast<int>((1-v)*ny-0.001);
1778+
// Clamp input texture coordinates to [0,1] x [1,0]
1779+
u = clamp(u, 0.0, 1.0);
1780+
v = 1.0 - clamp(v, 0.0, 1.0); // Flip V to image coordinates
17621781

1763-
if (i < 0) i = 0;
1764-
if (j < 0) j = 0;
1765-
if (i > nx-1) i = nx-1;
1766-
if (j > ny-1) j = ny-1;
1782+
auto i = static_cast<int>(u * width);
1783+
auto j = static_cast<int>(v * height);
17671784

1768-
auto r = static_cast<int>(data[3*i + 3*nx*j+0]) / 255.0;
1769-
auto g = static_cast<int>(data[3*i + 3*nx*j+1]) / 255.0;
1770-
auto b = static_cast<int>(data[3*i + 3*nx*j+2]) / 255.0;
1785+
// Clamp integer mapping, since actual coordinates should be less than 1.0
1786+
if (i >= width) i = width-1;
1787+
if (j >= height) j = height-1;
17711788

1772-
return color(r, g, b);
1789+
const auto color_scale = 1.0 / 255.0;
1790+
auto pixel = data + j*bytes_per_scanline + i*bytes_per_pixel;
1791+
1792+
return color(color_scale*pixel[0], color_scale*pixel[1], color_scale*pixel[2]);
17731793
}
17741794

1775-
public:
1795+
private:
17761796
unsigned char *data;
1777-
int nx, ny;
1797+
int width, height;
1798+
int bytes_per_scanline;
17781799
};
1800+
17791801
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
17801802
[Listing [img-texture]: <kbd>[texture.h]</kbd> Image texture class]
17811803

@@ -1809,11 +1831,8 @@
18091831

18101832
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ C++
18111833
hittable_list earth() {
1812-
int nx, ny, nn;
1813-
unsigned char* texture_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
1814-
1815-
auto earth_surface =
1816-
make_shared<lambertian>(make_shared<image_texture>(texture_data, nx, ny));
1834+
auto earth_texture = make_shared<image_texture>("earthmap.jpg");
1835+
auto earth_surface = make_shared<lambertian>(earth_texture);
18171836
auto globe = make_shared<sphere>(point3(0,0,0), 2, earth_surface);
18181837

18191838
return hittable_list(globe);
@@ -2861,9 +2880,7 @@
28612880
objects.add(make_shared<constant_medium>(
28622881
boundary, .0001, make_shared<constant_texture>(color(1,1,1))));
28632882

2864-
int nx, ny, nn;
2865-
auto tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
2866-
auto emat = make_shared<lambertian>(make_shared<image_texture>(tex_data, nx, ny));
2883+
auto emat = make_shared<lambertian>(make_shared<image_texture>("earthmap.jpg"));
28672884
objects.add(make_shared<sphere>(point3(400,200,400), 100, emat));
28682885
auto pertext = make_shared<noise_texture>(0.1);
28692886
objects.add(make_shared<sphere>(point3(220,280,300), 80, make_shared<lambertian>(pertext)));

src/TheNextWeek/main.cc

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
#include "hittable_list.h"
1919
#include "material.h"
2020
#include "moving_sphere.h"
21-
#include "rtw_stb_image.h"
2221
#include "sphere.h"
2322
#include "texture.h"
2423

@@ -129,11 +128,8 @@ hittable_list two_perlin_spheres() {
129128

130129

131130
hittable_list earth() {
132-
int nx, ny, nn;
133-
unsigned char* texture_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
134-
135-
auto earth_surface =
136-
make_shared<lambertian>(make_shared<image_texture>(texture_data, nx, ny));
131+
auto earth_texture = make_shared<image_texture>("earthmap.jpg");
132+
auto earth_surface = make_shared<lambertian>(earth_texture);
137133
auto globe = make_shared<sphere>(point3(0,0,0), 2, earth_surface);
138134

139135
return hittable_list(globe);
@@ -251,10 +247,7 @@ hittable_list cornell_final() {
251247

252248
auto pertext = make_shared<noise_texture>(0.1);
253249

254-
int nx, ny, nn;
255-
unsigned char* tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
256-
257-
auto mat = make_shared<lambertian>(make_shared<image_texture>(tex_data, nx, ny));
250+
auto mat = make_shared<lambertian>(make_shared<image_texture>("earthmap.jpg"));
258251

259252
auto red = make_shared<lambertian>(make_shared<constant_texture>(color(.65, .05, .05)));
260253
auto white = make_shared<lambertian>(make_shared<constant_texture>(color(.73, .73, .73)));
@@ -329,9 +322,7 @@ hittable_list final_scene() {
329322
objects.add(make_shared<constant_medium>(
330323
boundary, .0001, make_shared<constant_texture>(color(1,1,1))));
331324

332-
int nx, ny, nn;
333-
auto tex_data = stbi_load("earthmap.jpg", &nx, &ny, &nn, 0);
334-
auto emat = make_shared<lambertian>(make_shared<image_texture>(tex_data, nx, ny));
325+
auto emat = make_shared<lambertian>(make_shared<image_texture>("earthmap.jpg"));
335326
objects.add(make_shared<sphere>(point3(400,200,400), 100, emat));
336327
auto pertext = make_shared<noise_texture>(0.1);
337328
objects.add(make_shared<sphere>(point3(220,280,300), 80, make_shared<lambertian>(pertext)));

src/common/texture.h

Lines changed: 37 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414
#include "rtweekend.h"
1515

1616
#include "perlin.h"
17+
#include "rtw_stb_image.h"
18+
19+
#include <iostream>
1720

1821

1922
class texture {
@@ -74,36 +77,55 @@ class noise_texture : public texture {
7477

7578
class image_texture : public texture {
7679
public:
77-
image_texture() {}
78-
image_texture(unsigned char *pixels, int A, int B) : data(pixels), nx(A), ny(B) {}
80+
const static int bytes_per_pixel = 3;
81+
82+
image_texture()
83+
: data(nullptr), width(0), height(0), bytes_per_scanline(0) {}
84+
85+
image_texture(const char* filename) {
86+
auto components_per_pixel = bytes_per_pixel;
87+
88+
data = stbi_load(
89+
filename, &width, &height, &components_per_pixel, components_per_pixel);
90+
91+
if (!data) {
92+
std::cerr << "ERROR: Could not load texture image file '" << filename << "'.\n";
93+
width = height = 0;
94+
}
95+
96+
bytes_per_scanline = bytes_per_pixel * width;
97+
}
7998

8099
~image_texture() {
81100
delete data;
82101
}
83102

84103
virtual color value(double u, double v, const vec3& p) const {
85-
// If we have no texture data, then always emit cyan (as a debugging aid).
104+
// If we have no texture data, then return solid cyan as a debugging aid.
86105
if (data == nullptr)
87106
return color(0,1,1);
88107

89-
auto i = static_cast<int>(( u)*nx);
90-
auto j = static_cast<int>((1-v)*ny-0.001);
108+
// Clamp input texture coordinates to [0,1] x [1,0]
109+
u = clamp(u, 0.0, 1.0);
110+
v = 1.0 - clamp(v, 0.0, 1.0); // Flip V to image coordinates
91111

92-
if (i < 0) i = 0;
93-
if (j < 0) j = 0;
94-
if (i > nx-1) i = nx-1;
95-
if (j > ny-1) j = ny-1;
112+
auto i = static_cast<int>(u * width);
113+
auto j = static_cast<int>(v * height);
96114

97-
auto r = static_cast<int>(data[3*i + 3*nx*j+0]) / 255.0;
98-
auto g = static_cast<int>(data[3*i + 3*nx*j+1]) / 255.0;
99-
auto b = static_cast<int>(data[3*i + 3*nx*j+2]) / 255.0;
115+
// Clamp integer mapping, since actual coordinates should be less than 1.0
116+
if (i >= width) i = width-1;
117+
if (j >= height) j = height-1;
100118

101-
return color(r, g, b);
119+
const auto color_scale = 1.0 / 255.0;
120+
auto pixel = data + j*bytes_per_scanline + i*bytes_per_pixel;
121+
122+
return color(color_scale*pixel[0], color_scale*pixel[1], color_scale*pixel[2]);
102123
}
103124

104-
public:
125+
private:
105126
unsigned char *data;
106-
int nx, ny;
127+
int width, height;
128+
int bytes_per_scanline;
107129
};
108130

109131

0 commit comments

Comments
 (0)