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 
     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