blob: 7a56619c6634b0cf1a7e0dd3405069ecda6c6173 [file] [log] [blame]
// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <stdio.h>
#include <stdlib.h>
#include "ppapi/c/ppb_image_data.h"
#include "ppapi/cpp/graphics_2d.h"
#include "ppapi/cpp/image_data.h"
#include "ppapi/cpp/input_event.h"
#include "ppapi/cpp/instance.h"
#include "ppapi/cpp/module.h"
#include "ppapi/cpp/point.h"
#include "ppapi/utility/completion_callback_factory.h"
#ifdef WIN32
#undef PostMessage
// Allow 'this' in initializer list
#pragma warning(disable : 4355)
#endif
namespace {
static const int kMouseRadius = 20;
uint8_t RandUint8(uint8_t min, uint8_t max) {
uint64_t r = rand();
uint8_t result = static_cast<uint8_t>(r * (max - min + 1) / RAND_MAX) + min;
return result;
}
uint32_t MakeColor(uint8_t r, uint8_t g, uint8_t b) {
uint8_t a = 255;
PP_ImageDataFormat format = pp::ImageData::GetNativeImageDataFormat();
if (format == PP_IMAGEDATAFORMAT_BGRA_PREMUL) {
return (a << 24) | (r << 16) | (g << 8) | b;
} else {
return (a << 24) | (b << 16) | (g << 8) | r;
}
}
} // namespace
class Graphics2DInstance : public pp::Instance {
public:
explicit Graphics2DInstance(PP_Instance instance)
: pp::Instance(instance),
callback_factory_(this),
mouse_down_(false),
buffer_(NULL),
device_scale_(1.0f) {}
~Graphics2DInstance() { delete[] buffer_; }
virtual bool Init(uint32_t argc, const char* argn[], const char* argv[]) {
RequestInputEvents(PP_INPUTEVENT_CLASS_MOUSE);
unsigned int seed = 1;
srand(seed);
CreatePalette();
return true;
}
virtual void DidChangeView(const pp::View& view) {
device_scale_ = view.GetDeviceScale();
pp::Size new_size = pp::Size(view.GetRect().width() * device_scale_,
view.GetRect().height() * device_scale_);
if (!CreateContext(new_size))
return;
// When flush_context_ is null, it means there is no Flush callback in
// flight. This may have happened if the context was not created
// successfully, or if this is the first call to DidChangeView (when the
// module first starts). In either case, start the main loop.
if (flush_context_.is_null())
MainLoop(0);
}
virtual bool HandleInputEvent(const pp::InputEvent& event) {
if (!buffer_)
return true;
if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEDOWN ||
event.GetType() == PP_INPUTEVENT_TYPE_MOUSEMOVE) {
pp::MouseInputEvent mouse_event(event);
if (mouse_event.GetButton() == PP_INPUTEVENT_MOUSEBUTTON_NONE)
return true;
mouse_ = pp::Point(mouse_event.GetPosition().x() * device_scale_,
mouse_event.GetPosition().y() * device_scale_);
mouse_down_ = true;
}
if (event.GetType() == PP_INPUTEVENT_TYPE_MOUSEUP)
mouse_down_ = false;
return true;
}
private:
void CreatePalette() {
for (int i = 0; i < 64; ++i) {
// Black -> Red
palette_[i] = MakeColor(i * 2, 0, 0);
palette_[i + 64] = MakeColor(128 + i * 2, 0, 0);
// Red -> Yellow
palette_[i + 128] = MakeColor(255, i * 4, 0);
// Yellow -> White
palette_[i + 192] = MakeColor(255, 255, i * 4);
}
}
bool CreateContext(const pp::Size& new_size) {
const bool kIsAlwaysOpaque = true;
context_ = pp::Graphics2D(this, new_size, kIsAlwaysOpaque);
// Call SetScale before BindGraphics so the image is scaled correctly on
// HiDPI displays.
context_.SetScale(1.0f / device_scale_);
if (!BindGraphics(context_)) {
fprintf(stderr, "Unable to bind 2d context!\n");
context_ = pp::Graphics2D();
return false;
}
// Allocate a buffer of palette entries of the same size as the new context.
buffer_ = new uint8_t[new_size.width() * new_size.height()];
size_ = new_size;
return true;
}
void Update() {
// Old-school fire technique cribbed from
// http://ionicsolutions.net/2011/12/30/demo-fire-effect/
UpdateCoals();
DrawMouse();
UpdateFlames();
}
void UpdateCoals() {
int width = size_.width();
int height = size_.height();
size_t span = 0;
// Draw two rows of random values at the bottom.
for (int y = height - 2; y < height; ++y) {
size_t offset = y * width;
for (int x = 0; x < width; ++x) {
// On a random chance, draw some longer strips of brighter colors.
if (span || RandUint8(1, 4) == 1) {
if (!span)
span = RandUint8(10, 20);
buffer_[offset + x] = RandUint8(128, 255);
span--;
} else {
buffer_[offset + x] = RandUint8(32, 96);
}
}
}
}
void UpdateFlames() {
int width = size_.width();
int height = size_.height();
for (int y = 1; y < height - 1; ++y) {
size_t offset = y * width;
for (int x = 1; x < width - 1; ++x) {
int sum = 0;
sum += buffer_[offset - width + x - 1];
sum += buffer_[offset - width + x + 1];
sum += buffer_[offset + x - 1];
sum += buffer_[offset + x + 1];
sum += buffer_[offset + width + x - 1];
sum += buffer_[offset + width + x];
sum += buffer_[offset + width + x + 1];
buffer_[offset - width + x] = sum / 7;
}
}
}
void DrawMouse() {
if (!mouse_down_)
return;
int width = size_.width();
int height = size_.height();
// Draw a circle at the mouse position.
int radius = kMouseRadius * device_scale_;
int cx = mouse_.x();
int cy = mouse_.y();
int minx = cx - radius <= 0 ? 1 : cx - radius;
int maxx = cx + radius >= width ? width - 1 : cx + radius;
int miny = cy - radius <= 0 ? 1 : cy - radius;
int maxy = cy + radius >= height ? height - 1 : cy + radius;
for (int y = miny; y < maxy; ++y) {
for (int x = minx; x < maxx; ++x) {
if ((x - cx) * (x - cx) + (y - cy) * (y - cy) < radius * radius)
buffer_[y * width + x] = RandUint8(192, 255);
}
}
}
void Paint() {
// See the comment above the call to ReplaceContents below.
PP_ImageDataFormat format = pp::ImageData::GetNativeImageDataFormat();
const bool kDontInitToZero = false;
pp::ImageData image_data(this, format, size_, kDontInitToZero);
uint32_t* data = static_cast<uint32_t*>(image_data.data());
if (!data)
return;
uint32_t num_pixels = size_.width() * size_.height();
size_t offset = 0;
for (uint32_t i = 0; i < num_pixels; ++i) {
data[offset] = palette_[buffer_[offset]];
offset++;
}
// Using Graphics2D::ReplaceContents is the fastest way to update the
// entire canvas every frame. According to the documentation:
//
// Normally, calling PaintImageData() requires that the browser copy
// the pixels out of the image and into the graphics context's backing
// store. This function replaces the graphics context's backing store
// with the given image, avoiding the copy.
//
// In the case of an animation, you will want to allocate a new image for
// the next frame. It is best if you wait until the flush callback has
// executed before allocating this bitmap. This gives the browser the
// option of caching the previous backing store and handing it back to
// you (assuming the sizes match). In the optimal case, this means no
// bitmaps are allocated during the animation, and the backing store and
// "front buffer" (which the module is painting into) are just being
// swapped back and forth.
//
context_.ReplaceContents(&image_data);
}
void MainLoop(int32_t) {
if (context_.is_null()) {
// The current Graphics2D context is null, so updating and rendering is
// pointless. Set flush_context_ to null as well, so if we get another
// DidChangeView call, the main loop is started again.
flush_context_ = context_;
return;
}
Update();
Paint();
// Store a reference to the context that is being flushed; this ensures
// the callback is called, even if context_ changes before the flush
// completes.
flush_context_ = context_;
context_.Flush(
callback_factory_.NewCallback(&Graphics2DInstance::MainLoop));
}
pp::CompletionCallbackFactory<Graphics2DInstance> callback_factory_;
pp::Graphics2D context_;
pp::Graphics2D flush_context_;
pp::Size size_;
pp::Point mouse_;
bool mouse_down_;
uint8_t* buffer_;
uint32_t palette_[256];
float device_scale_;
};
class Graphics2DModule : public pp::Module {
public:
Graphics2DModule() : pp::Module() {}
virtual ~Graphics2DModule() {}
virtual pp::Instance* CreateInstance(PP_Instance instance) {
return new Graphics2DInstance(instance);
}
};
namespace pp {
Module* CreateModule() { return new Graphics2DModule(); }
} // namespace pp