1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include <stdio.h> 6 #include <stdlib.h> 7 8 #include "ppapi/c/ppb_image_data.h" 9 #include "ppapi/cpp/graphics_2d.h" 10 #include "ppapi/cpp/image_data.h" 11 #include "ppapi/cpp/input_event.h" 12 #include "ppapi/cpp/instance.h" 13 #include "ppapi/cpp/module.h" 14 #include "ppapi/cpp/point.h" 15 #include "ppapi/utility/completion_callback_factory.h" 16 17 #ifdef WIN32 18 #undef PostMessage 19 // Allow 'this' in initializer list 20 #pragma warning(disable : 4355) 21 #endif 22 23 namespace { 24 25 static const int kMouseRadius = 20; 26 27 uint8_t RandUint8(uint8_t min, uint8_t max) { 28 uint64_t r = rand(); 29 uint8_t result = static_cast<uint8_t>(r * (max - min + 1) / RAND_MAX) + min; 30 return result; 31 } 32 33 uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b) { 34 uint8_t a = 255; 35 PP_ImageDataFormat format = pp::ImageData::GetNativeImageDataFormat(); 36 if (format == PP_IMAGEDATAFORMAT_BGRA_PREMUL) { 37 return (a << 24) | (r << 16) | (g << 8) | b; 38 } else { 39 return (a << 24) | (b << 16) | (g << 8) | r; 40 } 41 } 42 43 } // namespace 44 45 class Graphics2DInstance : public pp::Instance { 46 public: 47 explicit Graphics2DInstance(PP_Instance instance) 48 : pp::Instance(instance), 49 callback_factory_(this), 50 mouse_down_(false), 51 buffer_(NULL) {} 52 53 ~Graphics2DInstance() { delete[] buffer_; } 54 55 virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) { 56 RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE); 57 58 unsigned int seed = 1; 59 srand(seed); 60 CreatePalette(); 61 return true; 62 } 63 64 virtual void DidChangeView(const pp::View& view) { 65 pp::Size new_size = view.GetRect().size(); 66 67 if (!CreateContext(new_size)) 68 return; 69 70 // When flush_context_ is null, it means there is no Flush callback in 71 // flight. This may have happened if the context was not created 72 // successfully, or if this is the first call to DidChangeView (when the 73 // module first starts). In either case, start the main loop. 74 if (flush_context_.is_null()) 75 MainLoop(0); 76 } 77 78 virtual bool HandleInputEvent(const pp::InputEvent& event) { 79 if (!buffer_) 80 return true; 81 82 if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN || 83 event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE) { 84 pp::MouseInputEvent mouse_event(event); 85 86 if (mouse_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_NONE) 87 return true; 88 89 mouse_ = mouse_event.GetPosition(); 90 mouse_down_ = true; 91 } 92 93 if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP) 94 mouse_down_ = false; 95 96 return true; 97 } 98 99 private: 100 void CreatePalette() { 101 for (int i = 0; i < 64; ++i) { 102 // Black -> Red 103 palette_[i] = MakeColor(i * 2, 0, 0); 104 palette_[i + 64] = MakeColor(128 + i * 2, 0, 0); 105 // Red -> Yellow 106 palette_[i + 128] = MakeColor(255, i * 4, 0); 107 // Yellow -> White 108 palette_[i + 192] = MakeColor(255, 255, i * 4); 109 } 110 } 111 112 bool CreateContext(const pp::Size& new_size) { 113 const bool kIsAlwaysOpaque = true; 114 context_ = pp::Graphics2D(this, new_size, kIsAlwaysOpaque); 115 if (!BindGraphics(context_)) { 116 fprintf(stderr, "Unable to bind 2d context!\n"); 117 context_ = pp::Graphics2D(); 118 return false; 119 } 120 121 // Allocate a buffer of palette entries of the same size as the new context. 122 buffer_ = new uint8_t[new_size.width() * new_size.height()]; 123 size_ = new_size; 124 125 return true; 126 } 127 128 void Update() { 129 // Old-school fire technique cribbed from 130 // http://ionicsolutions.net/2011/12/30/demo-fire-effect/ 131 UpdateCoals(); 132 DrawMouse(); 133 UpdateFlames(); 134 } 135 136 void UpdateCoals() { 137 int width = size_.width(); 138 int height = size_.height(); 139 size_t span = 0; 140 141 // Draw two rows of random values at the bottom. 142 for (int y = height - 2; y < height; ++y) { 143 size_t offset = y * width; 144 for (int x = 0; x < width; ++x) { 145 // On a random chance, draw some longer strips of brighter colors. 146 if (span || RandUint8(1, 4) == 1) { 147 if (!span) 148 span = RandUint8(10, 20); 149 buffer_[offset + x] = RandUint8(128, 255); 150 span--; 151 } else { 152 buffer_[offset + x] = RandUint8(32, 96); 153 } 154 } 155 } 156 } 157 158 void UpdateFlames() { 159 int width = size_.width(); 160 int height = size_.height(); 161 for (int y = 1; y < height - 1; ++y) { 162 size_t offset = y * width; 163 for (int x = 1; x < width - 1; ++x) { 164 int sum = 0; 165 sum += buffer_[offset - width + x - 1]; 166 sum += buffer_[offset - width + x + 1]; 167 sum += buffer_[offset + x - 1]; 168 sum += buffer_[offset + x + 1]; 169 sum += buffer_[offset + width + x - 1]; 170 sum += buffer_[offset + width + x]; 171 sum += buffer_[offset + width + x + 1]; 172 buffer_[offset - width + x] = sum / 7; 173 } 174 } 175 } 176 177 void DrawMouse() { 178 if (!mouse_down_) 179 return; 180 181 int width = size_.width(); 182 int height = size_.height(); 183 184 // Draw a circle at the mouse position. 185 int radius = kMouseRadius; 186 int cx = mouse_.x(); 187 int cy = mouse_.y(); 188 int minx = cx - radius <= 0 ? 1 : cx - radius; 189 int maxx = cx + radius >= width ? width - 1 : cx + radius; 190 int miny = cy - radius <= 0 ? 1 : cy - radius; 191 int maxy = cy + radius >= height ? height - 1 : cy + radius; 192 for (int y = miny; y < maxy; ++y) { 193 for (int x = minx; x < maxx; ++x) { 194 if ((x - cx) * (x - cx) + (y - cy) * (y - cy) < radius * radius) 195 buffer_[y * width + x] = RandUint8(192, 255); 196 } 197 } 198 } 199 200 void Paint() { 201 // See the comment above the call to ReplaceContents below. 202 PP_ImageDataFormat format = pp::ImageData::GetNativeImageDataFormat(); 203 const bool kDontInitToZero = false; 204 pp::ImageData image_data(this, format, size_, kDontInitToZero); 205 206 uint32_t* data = static_cast<uint32_t*>(image_data.data()); 207 if (!data) 208 return; 209 210 uint32_t num_pixels = size_.width() * size_.height(); 211 size_t offset = 0; 212 for (uint32_t i = 0; i < num_pixels; ++i) { 213 data[offset] = palette_[buffer_[offset]]; 214 offset++; 215 } 216 217 // Using Graphics2D::ReplaceContents is the fastest way to update the 218 // entire canvas every frame. According to the documentation: 219 // 220 // Normally, calling PaintImageData() requires that the browser copy 221 // the pixels out of the image and into the graphics context's backing 222 // store. This function replaces the graphics context's backing store 223 // with the given image, avoiding the copy. 224 // 225 // In the case of an animation, you will want to allocate a new image for 226 // the next frame. It is best if you wait until the flush callback has 227 // executed before allocating this bitmap. This gives the browser the 228 // option of caching the previous backing store and handing it back to 229 // you (assuming the sizes match). In the optimal case, this means no 230 // bitmaps are allocated during the animation, and the backing store and 231 // "front buffer" (which the module is painting into) are just being 232 // swapped back and forth. 233 // 234 context_.ReplaceContents(&image_data); 235 } 236 237 void MainLoop(int32_t) { 238 if (context_.is_null()) { 239 // The current Graphics2D context is null, so updating and rendering is 240 // pointless. Set flush_context_ to null as well, so if we get another 241 // DidChangeView call, the main loop is started again. 242 flush_context_ = context_; 243 return; 244 } 245 246 Update(); 247 Paint(); 248 // Store a reference to the context that is being flushed; this ensures 249 // the callback is called, even if context_ changes before the flush 250 // completes. 251 flush_context_ = context_; 252 context_.Flush( 253 callback_factory_.NewCallback(&Graphics2DInstance::MainLoop)); 254 } 255 256 pp::CompletionCallbackFactory<Graphics2DInstance> callback_factory_; 257 pp::Graphics2D context_; 258 pp::Graphics2D flush_context_; 259 pp::Size size_; 260 pp::Point mouse_; 261 bool mouse_down_; 262 uint8_t* buffer_; 263 uint32_t palette_[256]; 264 }; 265 266 class Graphics2DModule : public pp::Module { 267 public: 268 Graphics2DModule() : pp::Module() {} 269 virtual ~Graphics2DModule() {} 270 271 virtual pp::Instance* CreateInstance(PP_Instance instance) { 272 return new Graphics2DInstance(instance); 273 } 274 }; 275 276 namespace pp { 277 Module* CreateModule() { return new Graphics2DModule(); } 278 } // namespace pp 279