Home | History | Annotate | Download | only in graphics_2d
      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         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 
    274 class 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 
    284 namespace pp {
    285 Module* CreateModule() { return new Graphics2DModule(); }
    286 }  // namespace pp
    287