gosu 1.4.6 → 2.0.0.pre6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (170) hide show
  1. checksums.yaml +4 -4
  2. data/COPYING +2 -1
  3. data/dependencies/SDL/include/SDL_atomic.h +2 -3
  4. data/dependencies/SDL/include/SDL_audio.h +7 -7
  5. data/dependencies/SDL/include/SDL_blendmode.h +1 -1
  6. data/dependencies/SDL/include/SDL_endian.h +3 -3
  7. data/dependencies/SDL/include/SDL_gamecontroller.h +4 -4
  8. data/dependencies/SDL/include/SDL_hints.h +72 -28
  9. data/dependencies/SDL/include/SDL_joystick.h +8 -5
  10. data/dependencies/SDL/include/SDL_keycode.h +1 -1
  11. data/dependencies/SDL/include/SDL_main.h +7 -0
  12. data/dependencies/SDL/include/SDL_mouse.h +6 -7
  13. data/dependencies/SDL/include/SDL_mutex.h +79 -5
  14. data/dependencies/SDL/include/SDL_opengl_glext.h +5 -1
  15. data/dependencies/SDL/include/SDL_power.h +7 -8
  16. data/dependencies/SDL/include/SDL_render.h +5 -0
  17. data/dependencies/SDL/include/SDL_revision.h +2 -2
  18. data/dependencies/SDL/include/SDL_sensor.h +1 -1
  19. data/dependencies/SDL/include/SDL_stdinc.h +19 -11
  20. data/dependencies/SDL/include/SDL_thread.h +2 -2
  21. data/dependencies/SDL/include/SDL_version.h +2 -2
  22. data/dependencies/SDL/include/SDL_video.h +30 -2
  23. data/dependencies/SDL/include/begin_code.h +7 -7
  24. data/dependencies/SDL/include/close_code.h +2 -2
  25. data/dependencies/SDL/lib/x64/libSDL2.dll.a +0 -0
  26. data/dependencies/SDL/lib/x86/libSDL2.dll.a +0 -0
  27. data/dependencies/SDL_sound/SDL_sound.h +1 -1
  28. data/dependencies/SDL_sound/dr_flac.h +48 -23
  29. data/dependencies/SDL_sound/dr_mp3.h +34 -14
  30. data/dependencies/SDL_sound/stb_vorbis.h +3 -2
  31. data/dependencies/mojoAL/mojoal.c +1 -1
  32. data/ext/{gosu → gosu-ffi}/extconf.rb +34 -33
  33. data/ext/gosu-ffi/gosu-ffi.def +464 -0
  34. data/ffi/Gosu.cpp +307 -0
  35. data/ffi/Gosu.h +84 -0
  36. data/ffi/Gosu_Channel.cpp +62 -0
  37. data/ffi/Gosu_Channel.h +17 -0
  38. data/ffi/Gosu_Color.cpp +132 -0
  39. data/ffi/Gosu_Color.h +31 -0
  40. data/ffi/Gosu_Constants.cpp +334 -0
  41. data/ffi/Gosu_FFI.h +34 -0
  42. data/ffi/Gosu_FFI_internal.h +161 -0
  43. data/ffi/Gosu_Font.cpp +92 -0
  44. data/ffi/Gosu_Font.h +32 -0
  45. data/ffi/Gosu_Image.cpp +206 -0
  46. data/ffi/Gosu_Image.h +60 -0
  47. data/ffi/Gosu_Sample.cpp +29 -0
  48. data/ffi/Gosu_Sample.h +14 -0
  49. data/ffi/Gosu_Song.cpp +69 -0
  50. data/ffi/Gosu_Song.h +18 -0
  51. data/ffi/Gosu_TextInput.cpp +94 -0
  52. data/ffi/Gosu_TextInput.h +25 -0
  53. data/ffi/Gosu_Window.cpp +314 -0
  54. data/ffi/Gosu_Window.h +78 -0
  55. data/include/Gosu/Audio.hpp +6 -11
  56. data/include/Gosu/Bitmap.hpp +38 -53
  57. data/include/Gosu/Buffer.hpp +54 -0
  58. data/include/Gosu/Color.hpp +27 -35
  59. data/include/Gosu/Directories.hpp +25 -28
  60. data/include/Gosu/Drawable.hpp +58 -0
  61. data/include/Gosu/Font.hpp +6 -5
  62. data/include/Gosu/Fwd.hpp +4 -6
  63. data/include/Gosu/Gosu.hpp +5 -5
  64. data/include/Gosu/Graphics.hpp +51 -61
  65. data/include/Gosu/GraphicsBase.hpp +1 -11
  66. data/include/Gosu/Image.hpp +11 -14
  67. data/include/Gosu/Math.hpp +50 -72
  68. data/include/Gosu/Transform.hpp +32 -0
  69. data/include/Gosu/Utility.hpp +51 -1
  70. data/include/Gosu/Version.hpp +3 -3
  71. data/include/Gosu/Window.hpp +15 -9
  72. data/lib/SDL2.dll +0 -0
  73. data/lib/gosu/channel.rb +49 -0
  74. data/lib/gosu/color.rb +150 -0
  75. data/lib/gosu/compat.rb +29 -8
  76. data/lib/gosu/constants.rb +386 -0
  77. data/lib/gosu/ffi.rb +258 -0
  78. data/lib/gosu/font.rb +56 -0
  79. data/lib/gosu/gl_tex_info.rb +33 -0
  80. data/lib/gosu/gosu.rb +210 -0
  81. data/lib/gosu/image.rb +141 -0
  82. data/lib/gosu/numeric.rb +17 -0
  83. data/lib/gosu/preview.rb +6 -6
  84. data/lib/gosu/sample.rb +24 -0
  85. data/lib/gosu/song.rb +56 -0
  86. data/lib/gosu/text_input.rb +69 -0
  87. data/lib/gosu/window.rb +228 -0
  88. data/lib/gosu.rb +29 -8
  89. data/lib64/SDL2.dll +0 -0
  90. data/rdoc/gosu.rb +0 -2
  91. data/src/Audio.cpp +12 -12
  92. data/src/AudioFile.hpp +5 -4
  93. data/src/AudioFileAudioToolbox.cpp +8 -8
  94. data/src/AudioFileSDLSound.cpp +7 -10
  95. data/src/BinPacker.cpp +187 -0
  96. data/src/BinPacker.hpp +55 -0
  97. data/src/Bitmap.cpp +166 -144
  98. data/src/BitmapIO.cpp +60 -86
  99. data/src/Buffer.cpp +159 -0
  100. data/src/Color.cpp +75 -80
  101. data/src/Directories.cpp +47 -0
  102. data/src/DirectoriesUIKit.cpp +50 -0
  103. data/src/DrawOp.hpp +9 -4
  104. data/src/DrawOpQueue.hpp +2 -2
  105. data/src/Drawable.cpp +95 -0
  106. data/src/EmptyDrawable.hpp +38 -0
  107. data/src/FPS.cpp +31 -0
  108. data/src/Font.cpp +104 -74
  109. data/src/GosuGLView.cpp +14 -6
  110. data/src/GosuViewController.cpp +2 -10
  111. data/src/Graphics.cpp +60 -126
  112. data/src/GraphicsImpl.hpp +17 -47
  113. data/src/Image.cpp +41 -35
  114. data/src/Input.cpp +7 -8
  115. data/src/Macro.cpp +6 -6
  116. data/src/Macro.hpp +4 -4
  117. data/src/MarkupParser.cpp +5 -5
  118. data/src/Math.cpp +35 -22
  119. data/src/OffScreenTarget.cpp +53 -49
  120. data/src/OffScreenTarget.hpp +13 -11
  121. data/src/OpenGLContext.cpp +117 -0
  122. data/src/OpenGLContext.hpp +41 -0
  123. data/src/RenderState.hpp +21 -19
  124. data/src/Resolution.cpp +23 -21
  125. data/src/TexChunk.cpp +35 -80
  126. data/src/TexChunk.hpp +44 -35
  127. data/src/Text.cpp +1 -1
  128. data/src/TextBuilder.cpp +35 -21
  129. data/src/TextBuilder.hpp +6 -9
  130. data/src/Texture.cpp +62 -80
  131. data/src/Texture.hpp +25 -23
  132. data/src/TiledDrawable.cpp +150 -0
  133. data/src/TiledDrawable.hpp +47 -0
  134. data/src/TimingApple.cpp +1 -1
  135. data/src/Transform.cpp +45 -50
  136. data/src/TransformStack.hpp +16 -16
  137. data/src/TrueTypeFont.cpp +59 -51
  138. data/src/TrueTypeFont.hpp +6 -7
  139. data/src/TrueTypeFontApple.cpp +28 -19
  140. data/src/TrueTypeFontUnix.cpp +27 -23
  141. data/src/TrueTypeFontWin.cpp +30 -30
  142. data/src/Utility.cpp +84 -21
  143. data/src/UtilityWin.cpp +45 -0
  144. data/src/Window.cpp +92 -142
  145. data/src/WindowUIKit.cpp +14 -14
  146. metadata +72 -31
  147. data/include/Gosu/IO.hpp +0 -254
  148. data/include/Gosu/ImageData.hpp +0 -53
  149. data/include/Gosu/Inspection.hpp +0 -7
  150. data/lib/gosu/patches.rb +0 -66
  151. data/lib/gosu/run.rb +0 -20
  152. data/lib/gosu/swig_patches.rb +0 -110
  153. data/src/BlockAllocator.cpp +0 -131
  154. data/src/BlockAllocator.hpp +0 -32
  155. data/src/DirectoriesApple.cpp +0 -69
  156. data/src/DirectoriesUnix.cpp +0 -46
  157. data/src/DirectoriesWin.cpp +0 -65
  158. data/src/EmptyImageData.hpp +0 -52
  159. data/src/FileUnix.cpp +0 -99
  160. data/src/FileWin.cpp +0 -88
  161. data/src/IO.cpp +0 -60
  162. data/src/Iconv.hpp +0 -51
  163. data/src/Inspection.cpp +0 -27
  164. data/src/LargeImageData.cpp +0 -215
  165. data/src/LargeImageData.hpp +0 -39
  166. data/src/Log.hpp +0 -19
  167. data/src/RubyGosu.cxx +0 -13100
  168. data/src/RubyGosu.h +0 -49
  169. data/src/WinUtility.cpp +0 -61
  170. data/src/WinUtility.hpp +0 -27
data/src/BinPacker.hpp ADDED
@@ -0,0 +1,55 @@
1
+ #pragma once
2
+
3
+ #include <Gosu/Platform.hpp>
4
+ #include <Gosu/Utility.hpp>
5
+ #include <mutex>
6
+ #include <memory>
7
+ #include <vector>
8
+
9
+ namespace Gosu
10
+ {
11
+ /// This implements an allocator for 2D rects in a given "bin", which is typically an OpenGL
12
+ /// texture. It uses the GUILLOTINE-LAS-RM-BSSF algorithm because it has the best worst-case
13
+ /// performance in the PDF paper found here: https://github.com/juj/RectangleBinPack
14
+ /// (The extra complexity of the MAXRECTS algorithm did not seem to be worth it, and the Skyline
15
+ /// algorithm does not seem well-suited for scenarios where images are occasionally deleted.)
16
+ ///
17
+ /// Note: This class cannot use stb_rect_pack.h because that uses an "offline" algorithm,
18
+ /// i.e. it requires all boxes to be allocated at the same time, which is not how Gosu games are
19
+ /// usually structured.
20
+ ///
21
+ /// (This class is non-copyable because alloc returns shared_ptrs that reference this object.
22
+ /// Moving a BinPacker instance would lead to dangling pointers.)
23
+ class BinPacker : private Noncopyable
24
+ {
25
+ const int m_width, m_height;
26
+ std::vector<Rect> m_free_rects;
27
+ std::mutex m_mutex;
28
+
29
+ public:
30
+ BinPacker(int width, int height);
31
+
32
+ int width() const { return m_width; }
33
+ int height() const { return m_height; }
34
+
35
+ /// Finds a free rectangle in the bin and marks it as used, or returns nullptr.
36
+ /// The returned shared_ptr will automatically mark the rectangle as freed through its
37
+ /// deleter. The shared_ptr must not outlive the BinPacker.
38
+ std::shared_ptr<const Rect> alloc(int width, int height);
39
+ /// Marks a previously allocated rectangle as free again. This must be called with one of
40
+ /// the rectangles previously returned by alloc().
41
+ void add_free_rect(const Rect& rect);
42
+
43
+ private:
44
+ /// Finds the best free rectangle using the "Best Short Side Fit" ("BSSF") metric, if any.
45
+ const Rect* best_free_rect(int width, int height) const;
46
+
47
+ /// Removes m_free_rects[index]. If a pointer to another index is given, it will be adjusted
48
+ /// to the new index of the previously pointed-to rectangle (if it has moved).
49
+ void remove_free_rect(int index, int* other_index = nullptr);
50
+
51
+ /// Performs the "Rectangle Merge Improvement" (-RM) by repeatedly merging adjacent free
52
+ /// rects into m_free_rects[index] if they can be replaced by a single, larger rectangle.
53
+ void merge_neighbors(int index);
54
+ };
55
+ }
data/src/Bitmap.cpp CHANGED
@@ -1,200 +1,222 @@
1
1
  #include <Gosu/Bitmap.hpp>
2
2
  #include <Gosu/GraphicsBase.hpp>
3
+ #include <algorithm> // for std::equal, std::fill_n
4
+ #include <cstring> // for std::memcpy
5
+ #include <limits>
3
6
  #include <stdexcept> // for std::invalid_argument
4
- #include <utility> // for std::swap
7
+ #include <utility> // for std::move, std::swap
5
8
 
6
- Gosu::Bitmap::Bitmap(int width, int height, Gosu::Color c)
9
+ Gosu::Bitmap::Bitmap(int width, int height, Color c)
10
+ : m_width(width),
11
+ m_height(height)
7
12
  {
8
- resize(width, height, c);
13
+ if (width < 0 || height < 0) {
14
+ throw std::invalid_argument("Negative Gosu::Bitmap size");
15
+ }
16
+
17
+ // Don't allow bitmaps where there are more than INT_MAX pixels.
18
+ if (height != 0 && width > std::numeric_limits<int>::max() / height) {
19
+ throw std::invalid_argument("Gosu::Bitmap size out of bounds");
20
+ }
21
+
22
+ const int size = width * height;
23
+ m_pixels = Buffer(size * sizeof(Color));
24
+ std::fill_n(data(), size, c);
9
25
  }
10
26
 
11
- void Gosu::Bitmap::swap(Bitmap& other)
27
+ Gosu::Bitmap::Bitmap(int width, int height, Gosu::Buffer&& buffer)
28
+ : m_width(width),
29
+ m_height(height),
30
+ m_pixels(std::move(buffer))
12
31
  {
13
- std::swap(m_pixels, other.m_pixels);
14
- std::swap(m_width, other.m_width);
15
- std::swap(m_height, other.m_height);
32
+ int pixels = width * height;
33
+ if (static_cast<std::size_t>(pixels) * sizeof(Color) != m_pixels.size()) {
34
+ throw std::length_error("Gosu::Bitmap given Gosu::Buffer of wrong size, expected "
35
+ + std::to_string(pixels * sizeof(Color)) + ", given "
36
+ + std::to_string(m_pixels.size()));
37
+ }
16
38
  }
17
39
 
18
40
  void Gosu::Bitmap::resize(int width, int height, Color c)
19
41
  {
20
- if (width < 0 || height < 0) throw std::invalid_argument{"negative bitmap size"};
21
-
22
- if (width == m_width && height == m_height) return;
23
-
24
- Bitmap temp;
25
- temp.m_width = width;
26
- temp.m_height = height;
27
- temp.m_pixels.resize(width * height, c);
28
- temp.insert(0, 0, *this);
29
- swap(temp);
42
+ if (width != m_width || height != m_height) {
43
+ Bitmap temp(width, height, c);
44
+ temp.insert(*this, 0, 0);
45
+ std::swap(*this, temp);
46
+ }
30
47
  }
31
48
 
32
49
  void Gosu::Bitmap::blend_pixel(int x, int y, Color c)
33
50
  {
34
- if (c.alpha == 0) return;
51
+ if (c.alpha == 0) {
52
+ return;
53
+ }
35
54
 
36
- Color out = get_pixel(x, y);
37
- if (out.alpha == 0) {
38
- set_pixel(x, y, c);
55
+ Color& out = pixel(x, y);
56
+ if (out.alpha == 0 || c.alpha == 255) {
57
+ out = c;
39
58
  return;
40
59
  }
41
60
 
42
61
  int inv_alpha = out.alpha * (255 - c.alpha) / 255;
43
62
 
44
63
  out.alpha = (c.alpha + inv_alpha);
45
- out.red = ((c.red * c.alpha + out.red * inv_alpha) / out.alpha);
64
+ out.red = ((c.red * c.alpha + out.red * inv_alpha) / out.alpha);
46
65
  out.green = ((c.green * c.alpha + out.green * inv_alpha) / out.alpha);
47
- out.blue = ((c.blue * c.alpha + out.blue * inv_alpha) / out.alpha);
48
-
49
- set_pixel(x, y, out);
66
+ out.blue = ((c.blue * c.alpha + out.blue * inv_alpha) / out.alpha);
50
67
  }
51
68
 
52
- void Gosu::Bitmap::insert(int x, int y, const Bitmap& source)
69
+ void Gosu::Bitmap::insert(const Bitmap& source, int x, int y)
53
70
  {
54
- insert(x, y, source, 0, 0, source.width(), source.height());
71
+ insert(source, x, y, Rect::covering(source));
55
72
  }
56
73
 
57
- void Gosu::Bitmap::insert(int x, int y, const Bitmap& source,
58
- int src_x, int src_y, int src_width, int src_height)
74
+ void Gosu::Bitmap::insert(const Bitmap& source, int x, int y, Rect source_rect)
59
75
  {
60
- // TODO: This should use memcpy if possible (x == 0 && src_width == this->width())
61
-
62
- if (x < 0) {
63
- int clip_left = -x;
64
-
65
- if (clip_left >= src_width) return;
66
-
67
- src_x += clip_left;
68
- src_width -= clip_left;
69
- x = 0;
76
+ if (&source == this) {
77
+ throw std::invalid_argument("Gosu::Bitmap::insert cannot copy parts of itself");
70
78
  }
71
79
 
72
- if (y < 0) {
73
- int clip_top = -y;
74
-
75
- if (clip_top >= src_height) return;
80
+ // Make sure that the source area does not exceed the source image.
81
+ // If we need to move the source_rect origin, then also move the target rectangle origin.
82
+ source_rect.clip_to(Rect::covering(source), &x, &y);
76
83
 
77
- src_y += clip_top;
78
- src_height -= clip_top;
79
- y = 0;
80
- }
81
-
82
- if (x + src_width > m_width) {
83
- if (x >= m_width) return;
84
+ // Set up the target area and make sure that it does not exceed this image.
85
+ Rect target_rect { .x = x, .y = y, .width = source_rect.width, .height = source_rect.height };
86
+ // If we need to move the target_rect origin, then also move the source_rect origin.
87
+ target_rect.clip_to(Rect::covering(*this), &source_rect.x, &source_rect.y);
84
88
 
85
- src_width = m_width - x;
86
- }
89
+ // These are the first source and first destination pixels/rows.
90
+ Color* target_ptr = &pixel(target_rect.x, target_rect.y);
91
+ const Color* source_ptr = &source.pixel(source_rect.x, source_rect.y);
87
92
 
88
- if (y + src_height > m_height) {
89
- if (y >= m_height) return;
93
+ // target_rect might be smaller than source_rect now, so use its width/height for copying.
90
94
 
91
- src_height = m_height - y;
95
+ if (width() == source.width() && width() == target_rect.width) {
96
+ // If both images have the same width, and we want to copy full lines, then we can use a
97
+ // single memcpy for the whole operation. This is especially likely if we vertically resize
98
+ // a bitmap.
99
+ const int size = target_rect.width * target_rect.height;
100
+ std::memcpy(target_ptr, source_ptr, static_cast<std::size_t>(size) * sizeof(Color));
92
101
  }
93
-
94
- for (int rel_y = 0; rel_y < src_height; ++rel_y) {
95
- for (int rel_x = 0; rel_x < src_width; ++rel_x) {
96
- set_pixel(x + rel_x, y + rel_y, source.get_pixel(src_x + rel_x, src_y + rel_y));
102
+ else {
103
+ // Otherwise, we need to copy line by line.
104
+ for (int row = 0; row < target_rect.height; ++row) {
105
+ std::memcpy(target_ptr, source_ptr, target_rect.width * sizeof(Color));
106
+ target_ptr += width();
107
+ source_ptr += source.width();
97
108
  }
98
109
  }
99
110
  }
100
111
 
101
- void Gosu::apply_color_key(Bitmap& bitmap, Color key)
112
+ bool Gosu::Bitmap::operator==(const Gosu::Bitmap& other) const
102
113
  {
103
- for (int y = 0; y < bitmap.height(); ++y) {
104
- for (int x = 0; x < bitmap.width(); ++x) {
105
- if (bitmap.get_pixel(x, y) == key) {
106
- // Calculate the average R/G/B of adjacent, non-transparent pixels.
107
- unsigned neighbors = 0, red = 0, green = 0, blue = 0;
108
- auto visit = [&](Color c) {
109
- if (c != key) {
110
- neighbors += 1;
111
- red += c.red;
112
- green += c.green;
113
- blue += c.blue;
114
- }
115
- };
116
-
117
- if (x > 0) visit(bitmap.get_pixel(x - 1, y));
118
- if (x < bitmap.width() - 1) visit(bitmap.get_pixel(x + 1, y));
119
- if (y > 0) visit(bitmap.get_pixel(x, y - 1));
120
- if (y < bitmap.height() - 1) visit(bitmap.get_pixel(x, y + 1));
121
-
122
- Color replacement = Color::NONE;
123
- if (neighbors > 0) {
124
- replacement.red = red / neighbors;
125
- replacement.green = green / neighbors;
126
- replacement.blue = blue / neighbors;
127
- }
128
- bitmap.set_pixel(x, y, replacement);
129
- }
130
- }
131
- }
114
+ int pixels = width() * height();
115
+ return pixels == other.width() * other.height()
116
+ && std::equal(data(), data() + pixels, other.data());
132
117
  }
133
118
 
134
- Gosu::Bitmap Gosu::apply_border_flags(unsigned image_flags, const Bitmap& source,
135
- int src_x, int src_y, int src_width, int src_height)
119
+ void Gosu::Bitmap::apply_color_key(Color key)
136
120
  {
137
- // By default, we add one pixel of transparent data around the whole image to so that during
138
- // interpolation, the image just fades out, instead of bleeding into adjacent image data on
139
- // whatever shared texture atlas it ends up on.
140
- // However, if a border is marked as "tileable", we instead repeat the outermost pixels, which
141
- // leads to a nice sharp/hard border with no interpolation at all.
142
-
143
- // TODO: Instead of using Color::NONE (transparent black) for non-tileable image, still repeat
144
- // the border pixel colors, but turn them fully transparent.
145
-
146
- // Backward compatibility: This used to be 'bool tileable'.
147
- if (image_flags == 1) image_flags = IF_TILEABLE;
121
+ // The valid memory range from which the loop below can read.
122
+ const int pixels = width() * height();
123
+ Color* begin = data();
124
+ Color* end = begin + pixels;
125
+
126
+ for (Color* c = begin; c != end;) {
127
+ for (int x = 0; x < width(); ++x, ++c) {
128
+ // All colors except the color key should stay as they are.
129
+ if (*c != key) {
130
+ continue;
131
+ }
148
132
 
149
- Gosu::Bitmap dest{src_width + 2, src_height + 2};
133
+ unsigned neighbors = 0, red = 0, green = 0, blue = 0;
150
134
 
151
- // The borders are made "harder" by duplicating the original bitmap's
152
- // borders.
135
+ const auto visit = [&](const Color* neighbor) {
136
+ if (neighbor >= begin && neighbor < end && *neighbor != key && neighbor->alpha) {
137
+ // Ignore other pixels that are or were equal to the color key.
138
+ neighbors += 1;
139
+ red += neighbor->red;
140
+ green += neighbor->green;
141
+ blue += neighbor->blue;
142
+ }
143
+ };
144
+ // Don't look at (x-1) pixels in the first column because we might accidentally use
145
+ // the pixels of the last column through wraparound.
146
+ if (x != 0) {
147
+ visit(c - width() - 1);
148
+ visit(c - 1);
149
+ visit(c + width() - 1);
150
+ }
151
+ visit(c - width());
152
+ visit(c + width());
153
+ // Don't look at (x+1) pixels in the last column because we might accidentally use
154
+ // the pixels of the first column through wraparound.
155
+ if (x != width() - 1) {
156
+ visit(c - width() + 1);
157
+ visit(c + 1);
158
+ visit(c + width() + 1);
159
+ }
153
160
 
154
- // Top.
155
- if (image_flags & IF_TILEABLE_TOP) {
156
- dest.insert(1, 0,
157
- source, src_x, src_y, src_width, 1);
158
- }
159
- // Bottom.
160
- if (image_flags & IF_TILEABLE_BOTTOM) {
161
- dest.insert(1, dest.height() - 1,
162
- source, src_x, src_y + src_height - 1, src_width, 1);
163
- }
164
- // Left.
165
- if (image_flags & IF_TILEABLE_LEFT) {
166
- dest.insert(0, 1,
167
- source, src_x, src_y, 1, src_height);
168
- }
169
- // Right.
170
- if (image_flags & IF_TILEABLE_RIGHT) {
171
- dest.insert(dest.width() - 1, 1,
172
- source, src_x + src_width - 1, src_y, 1, src_height);
161
+ *c = Color::NONE;
162
+ if (neighbors > 0) {
163
+ c->red = red / neighbors;
164
+ c->green = green / neighbors;
165
+ c->blue = blue / neighbors;
166
+ }
167
+ }
173
168
  }
169
+ }
174
170
 
175
- // Top left.
176
- if ((image_flags & IF_TILEABLE_TOP) && (image_flags & IF_TILEABLE_LEFT)) {
177
- dest.set_pixel(0, 0,
178
- source.get_pixel(src_x, src_y));
171
+ Gosu::Bitmap Gosu::apply_border_flags(unsigned image_flags, const Bitmap& source, Rect source_rect)
172
+ {
173
+ // Add one extra pixel around all four sides of the image.
174
+ Gosu::Bitmap result(source_rect.width + 2, source_rect.height + 2);
175
+ result.insert(source, 1, 1, source_rect);
176
+
177
+ // Now duplicate the edges of the image on all four sides.
178
+ const Rect top { source_rect.x, source_rect.y, source_rect.width, 1 };
179
+ result.insert(source, 1, 0, top);
180
+ const Rect bottom { source_rect.x, source_rect.bottom() - 1, source_rect.width, 1 };
181
+ result.insert(source, 1, result.height() - 1, bottom);
182
+ const Rect left { source_rect.x, source_rect.y, 1, source_rect.height };
183
+ result.insert(source, 0, 1, left);
184
+ const Rect right { source_rect.right() - 1, source_rect.y, 1, source_rect.height };
185
+ result.insert(source, result.width() - 1, 1, right);
186
+ // Also duplicate the corners of each size.
187
+ const Rect top_left { source_rect.x, source_rect.y, 1, 1 };
188
+ result.insert(source, 0, 0, top_left);
189
+ const Rect top_right { source_rect.right() - 1, source_rect.y, 1, 1 };
190
+ result.insert(source, result.width() - 1, 0, top_right);
191
+ const Rect bottom_left { source_rect.x, source_rect.bottom() - 1, 1, 1 };
192
+ result.insert(source, 0, result.height() - 1, bottom_left);
193
+ const Rect bottom_right { source_rect.right() - 1, source_rect.bottom() - 1, 1, 1 };
194
+ result.insert(source, result.width() - 1, result.height() - 1, bottom_right);
195
+
196
+ // On edges which are supposed to have tileable borders, we are now finished.
197
+ // Where soft borders are desired, we need to make all pixels on a side fully transparent.
198
+ if ((image_flags & IF_TILEABLE_TOP) == 0) {
199
+ for (int x = 0; x < result.width(); ++x) {
200
+ result.pixel(x, 0).alpha = 0;
201
+ }
179
202
  }
180
- // Top right.
181
- if ((image_flags & IF_TILEABLE_TOP) && (image_flags & IF_TILEABLE_RIGHT)) {
182
- dest.set_pixel(dest.width() - 1, 0,
183
- source.get_pixel(src_x + src_width - 1, src_y));
203
+ if ((image_flags & IF_TILEABLE_BOTTOM) == 0) {
204
+ const int y = result.height() - 1;
205
+ for (int x = 0; x < result.width(); ++x) {
206
+ result.pixel(x, y).alpha = 0;
207
+ }
184
208
  }
185
- // Bottom left.
186
- if ((image_flags & IF_TILEABLE_BOTTOM) && (image_flags & IF_TILEABLE_LEFT)) {
187
- dest.set_pixel(0, dest.height() - 1,
188
- source.get_pixel(src_x, src_y + src_height - 1));
209
+ if ((image_flags & IF_TILEABLE_LEFT) == 0) {
210
+ for (int y = 0; y < result.height(); ++y) {
211
+ result.pixel(0, y).alpha = 0;
212
+ }
189
213
  }
190
- // Bottom right.
191
- if ((image_flags & IF_TILEABLE_BOTTOM) && (image_flags & IF_TILEABLE_RIGHT)) {
192
- dest.set_pixel(dest.width() - 1, dest.height() - 1,
193
- source.get_pixel(src_x + src_width - 1, src_y + src_height - 1));
214
+ if ((image_flags & IF_TILEABLE_RIGHT) == 0) {
215
+ const int x = result.width() - 1;
216
+ for (int y = 0; y < result.height(); ++y) {
217
+ result.pixel(x, y).alpha = 0;
218
+ }
194
219
  }
195
220
 
196
- // Now put the final image into the prepared borders.
197
- dest.insert(1, 1,
198
- source, src_x, src_y, src_width, src_height);
199
- return dest;
221
+ return result;
200
222
  }
data/src/BitmapIO.cpp CHANGED
@@ -1,94 +1,63 @@
1
1
  #include <Gosu/Bitmap.hpp>
2
2
  #include <Gosu/Utility.hpp>
3
- #include <cstring> // for std::memcpy, std::size_t
4
3
  #include <stdexcept> // for std::runtime_error
5
4
 
6
5
  #define STB_IMAGE_IMPLEMENTATION
7
6
  #define STBI_NO_STDIO
8
- #define STBI_NO_LINEAR
7
+ #define STBI_NO_LINEAR // we don't really care about this, but it avoids warnings
9
8
 
10
9
  #include <stb_image.h>
11
10
 
12
- static int read_callback(void* user, char* data, int size)
11
+ namespace
13
12
  {
14
- Gosu::Reader* reader = static_cast<Gosu::Reader*>(user);
15
- std::size_t remaining = reader->resource().size() - reader->position();
16
- std::size_t adjusted_size = (size < remaining ? size : remaining);
17
- reader->read(data, adjusted_size);
18
- return static_cast<int>(adjusted_size);
19
- }
20
-
21
- static void skip_callback(void* user, int n)
22
- {
23
- Gosu::Reader* reader = static_cast<Gosu::Reader*>(user);
24
- reader->set_position(reader->position() + n);
25
- }
26
-
27
- static int eof_callback(void* user)
28
- {
29
- Gosu::Reader* reader = static_cast<Gosu::Reader*>(user);
30
- return reader->position() == reader->resource().size();
31
- }
32
-
33
- static bool is_bmp(Gosu::Reader reader)
34
- {
35
- std::size_t remaining = reader.resource().size() - reader.position();
36
- if (remaining < 2) return false;
37
- char magic_bytes[2];
38
- reader.read(&magic_bytes, sizeof magic_bytes);
39
- return magic_bytes[0] == 'B' && magic_bytes[1] == 'M';
40
- }
41
-
42
- /// Returns a copy of the given Gosu::Image with the alpha channel removed.
43
- /// Every pixel with alpha==0 will be replaced by Gosu::Color::FUCHSIA.
44
- static std::vector<Gosu::Color::Channel> bitmap_to_rgb(const Gosu::Bitmap& bmp)
45
- {
46
- std::vector<Gosu::Color::Channel> rgb(static_cast<std::size_t>(bmp.width() * bmp.height() * 3));
47
- for (std::size_t offset = 0; offset < rgb.size(); offset += 3) {
48
- const Gosu::Color& color = bmp.data()[offset / 3];
49
- if (color.alpha == 0) {
50
- rgb[offset + 0] = 0xff;
51
- rgb[offset + 1] = 0x00;
52
- rgb[offset + 2] = 0xff;
53
- } else {
54
- rgb[offset + 0] = color.red;
55
- rgb[offset + 1] = color.green;
56
- rgb[offset + 2] = color.blue;
13
+ constexpr int JPEG_QUALITY = 80;
14
+
15
+ /// Returns a copy of the given Gosu::Image with the alpha channel removed.
16
+ /// Every pixel with alpha==0 will be replaced by Gosu::Color::FUCHSIA.
17
+ std::vector<Gosu::Color::Channel> remove_alpha_channel(const Gosu::Bitmap& bmp)
18
+ {
19
+ std::vector<Gosu::Color::Channel> rgb(
20
+ static_cast<std::size_t>(bmp.width() * bmp.height() * 3));
21
+ for (std::size_t offset = 0; offset < rgb.size(); offset += 3) {
22
+ const Gosu::Color& color = bmp.data()[offset / 3];
23
+ if (color.alpha == 0) {
24
+ rgb[offset + 0] = 0xff;
25
+ rgb[offset + 1] = 0x00;
26
+ rgb[offset + 2] = 0xff;
27
+ }
28
+ else {
29
+ rgb[offset + 0] = color.red;
30
+ rgb[offset + 1] = color.green;
31
+ rgb[offset + 2] = color.blue;
32
+ }
57
33
  }
34
+ return rgb;
58
35
  }
59
- return rgb;
60
36
  }
61
37
 
62
38
  Gosu::Bitmap Gosu::load_image_file(const std::string& filename)
63
39
  {
64
- Buffer buffer;
65
- load_file(buffer, filename);
66
- return load_image_file(buffer.front_reader());
40
+ return load_image(load_file(filename));
67
41
  }
68
42
 
69
- Gosu::Bitmap Gosu::load_image_file(Reader input)
43
+ Gosu::Bitmap Gosu::load_image(const Buffer& buffer)
70
44
  {
71
- bool needs_color_key = is_bmp(input);
72
-
73
- stbi_io_callbacks callbacks{};
74
- callbacks.read = read_callback;
75
- callbacks.skip = skip_callback;
76
- callbacks.eof = eof_callback;
77
45
  int x = 0, y = 0, n = 0;
78
-
79
- stbi_uc* bytes = stbi_load_from_callbacks(&callbacks, &input, &x, &y, &n, STBI_rgb_alpha);
46
+ stbi_uc* bytes = stbi_load_from_memory(buffer.data(), static_cast<int>(buffer.size()), &x, &y,
47
+ &n, STBI_rgb_alpha);
80
48
  if (bytes == nullptr) {
81
- throw std::runtime_error{"Cannot load image: " + std::string(stbi_failure_reason())};
49
+ throw std::runtime_error("Cannot load image: " + std::string(stbi_failure_reason()));
82
50
  }
83
51
 
84
- Gosu::Bitmap bitmap{x, y};
85
- std::memcpy(bitmap.data(), bytes, x * y * sizeof(Gosu::Color));
86
-
87
- stbi_image_free(bytes);
52
+ int pixels = x * y;
53
+ Buffer pixel_buffer(bytes, static_cast<std::size_t>(pixels) * sizeof(Color), &stbi_image_free);
54
+ Gosu::Bitmap bitmap(x, y, std::move(pixel_buffer));
88
55
 
89
- if (needs_color_key) {
90
- apply_color_key(bitmap, Gosu::Color::FUCHSIA);
56
+ // If we just read a BMP file, we want to apply a color key.
57
+ if (buffer.size() > 2 && buffer.data()[0] == 'B' && buffer.data()[1] == 'M') {
58
+ bitmap.apply_color_key(Gosu::Color::FUCHSIA);
91
59
  }
60
+
92
61
  return bitmap;
93
62
  }
94
63
 
@@ -101,10 +70,11 @@ void Gosu::save_image_file(const Gosu::Bitmap& bitmap, const std::string& filena
101
70
 
102
71
  if (has_extension(filename, "bmp")) {
103
72
  ok = stbi_write_bmp(filename.c_str(), bitmap.width(), bitmap.height(), 3,
104
- bitmap_to_rgb(bitmap).data());
73
+ remove_alpha_channel(bitmap).data());
105
74
  }
106
- else if (has_extension(filename, "tga")) {
107
- ok = stbi_write_tga(filename.c_str(), bitmap.width(), bitmap.height(), 4, bitmap.data());
75
+ else if (has_extension(filename, "jpg") || has_extension(filename, "jpeg")) {
76
+ ok = stbi_write_jpg(filename.c_str(), bitmap.width(), bitmap.height(), 3,
77
+ remove_alpha_channel(bitmap).data(), JPEG_QUALITY);
108
78
  }
109
79
  else {
110
80
  ok = stbi_write_png(filename.c_str(), bitmap.width(), bitmap.height(), 4, bitmap.data(), 0);
@@ -115,32 +85,36 @@ void Gosu::save_image_file(const Gosu::Bitmap& bitmap, const std::string& filena
115
85
  }
116
86
  }
117
87
 
118
- static void stbi_write_to_writer(void* context, void* data, int size)
119
- {
120
- static_cast<Gosu::Writer*>(context)->write(data, size);
121
- }
122
-
123
- void Gosu::save_image_file(const Gosu::Bitmap& bitmap, Gosu::Writer writer,
124
- const std::string_view& format_hint)
88
+ Gosu::Buffer Gosu::save_image(const Gosu::Bitmap& bitmap, std::string_view format_hint)
125
89
  {
90
+ const auto stbi_write_to_vector = [](void* context, void* data, int size) {
91
+ auto* vec = static_cast<std::vector<std::uint8_t>*>(context);
92
+ const auto* begin = static_cast<const std::uint8_t*>(data);
93
+ const auto* end = begin + size;
94
+ vec->insert(vec->end(), begin, end);
95
+ };
96
+
97
+ std::vector<std::uint8_t> vector;
126
98
  int ok;
127
99
 
128
- if (has_extension(format_hint, "bmp")) {
129
- ok = stbi_write_bmp_to_func(stbi_write_to_writer, &writer, bitmap.width(), bitmap.height(),
130
- 3, bitmap_to_rgb(bitmap).data());
100
+ if (format_hint == "bmp" || has_extension(format_hint, "bmp")) {
101
+ ok = stbi_write_bmp_to_func(stbi_write_to_vector, &vector, bitmap.width(), bitmap.height(),
102
+ 3, remove_alpha_channel(bitmap).data());
131
103
  }
132
- else if (has_extension(format_hint, "tga")) {
133
- stbi_write_tga_with_rle = 0;
134
- ok = stbi_write_tga_to_func(stbi_write_to_writer, &writer, bitmap.width(), bitmap.height(),
135
- 4, bitmap.data());
104
+ else if (format_hint == "jpg" || format_hint == "jpeg" || has_extension(format_hint, "jpg")
105
+ || has_extension(format_hint, "jpeg")) {
106
+ ok = stbi_write_jpg_to_func(stbi_write_to_vector, &vector, bitmap.width(), bitmap.height(),
107
+ 3, remove_alpha_channel(bitmap).data(), JPEG_QUALITY);
136
108
  }
137
109
  else {
138
- ok = stbi_write_png_to_func(stbi_write_to_writer, &writer, bitmap.width(), bitmap.height(),
110
+ ok = stbi_write_png_to_func(stbi_write_to_vector, &vector, bitmap.width(), bitmap.height(),
139
111
  4, bitmap.data(), 0);
140
112
  }
141
113
 
142
114
  if (ok == 0) {
143
- throw std::runtime_error("Could not save image data to memory (format hint = '" +
144
- std::string{format_hint} + "'");
115
+ throw std::runtime_error("Could not save image data to memory (format hint = '"
116
+ + std::string(format_hint) + "'");
145
117
  }
118
+
119
+ return Buffer(std::move(vector));
146
120
  }