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