123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263 |
- /*
- * Copyright (c) 2018-2020, Andreas Kling <kling@serenityos.org>
- * All rights reserved.
- *
- * Redistribution and use in source and binary forms, with or without
- * modification, are permitted provided that the following conditions are met:
- *
- * 1. Redistributions of source code must retain the above copyright notice, this
- * list of conditions and the following disclaimer.
- *
- * 2. Redistributions in binary form must reproduce the above copyright notice,
- * this list of conditions and the following disclaimer in the documentation
- * and/or other materials provided with the distribution.
- *
- * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
- * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
- * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
- * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
- * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
- * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
- * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
- * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
- * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
- * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
- */
- /* Fire.cpp - a (classic) graphics demo for Serenity, by pd.
- * heavily based on the Fabien Sanglard's article:
- * http://fabiensanglard.net/doom_fire_psx/index.html
- *
- * Future directions:
- * [X] This does suggest the need for a palletized graphics surface. Thanks kling!
- * [X] alternate column updates, or vertical interlacing. this would certainly alter
- * the effect, but the update load would be halved.
- * [/] scaled blit
- * [ ] dithering?
- * [X] inlining rand()
- * [/] precalculating and recycling random data
- * [ ] rework/expand palette
- * [ ] switch to use tsc values for perf check
- * [ ] handle mouse events differently for smoother painting (queue)
- * [ ] handle fire bitmap edges better
- */
- #include <LibDraw/GraphicsBitmap.h>
- #include <LibDraw/PNGLoader.h>
- #include <LibGUI/GApplication.h>
- #include <LibGUI/GLabel.h>
- #include <LibGUI/GPainter.h>
- #include <LibGUI/GWidget.h>
- #include <LibGUI/GWindow.h>
- #include <stdio.h>
- #include <stdlib.h>
- #include <time.h>
- #define FIRE_WIDTH 320
- #define FIRE_HEIGHT 168
- #define FIRE_MAX 29
- static const Color s_palette[] = {
- Color(0x07, 0x07, 0x07), Color(0x1F, 0x07, 0x07), Color(0x2F, 0x0F, 0x07),
- Color(0x47, 0x0F, 0x07), Color(0x57, 0x17, 0x07), Color(0x67, 0x1F, 0x07),
- Color(0x77, 0x1F, 0x07), Color(0x9F, 0x2F, 0x07), Color(0xAF, 0x3F, 0x07),
- Color(0xBF, 0x47, 0x07), Color(0xC7, 0x47, 0x07), Color(0xDF, 0x4F, 0x07),
- Color(0xDF, 0x57, 0x07), Color(0xD7, 0x5F, 0x07), Color(0xD7, 0x5F, 0x07),
- Color(0xD7, 0x67, 0x0F), Color(0xCF, 0x6F, 0x0F), Color(0xCF, 0x7F, 0x0F),
- Color(0xCF, 0x87, 0x17), Color(0xC7, 0x87, 0x17), Color(0xC7, 0x8F, 0x17),
- Color(0xC7, 0x97, 0x1F), Color(0xBF, 0x9F, 0x1F), Color(0xBF, 0xA7, 0x27),
- Color(0xBF, 0xAF, 0x2F), Color(0xB7, 0xAF, 0x2F), Color(0xB7, 0xB7, 0x37),
- Color(0xCF, 0xCF, 0x6F), Color(0xEF, 0xEF, 0xC7), Color(0xFF, 0xFF, 0xFF)
- };
- /* Random functions...
- * These are from musl libc's prng/rand.c
- */
- static uint64_t seed;
- void my_srand(unsigned s)
- {
- seed = s - 1;
- }
- static int my_rand(void)
- {
- seed = 6364136223846793005ULL * seed + 1;
- return seed >> 33;
- }
- /*
- * Fire Widget
- */
- class Fire : public GWidget {
- C_OBJECT(Fire)
- public:
- virtual ~Fire() override;
- void set_stat_label(GLabel* l) { stats = l; };
- private:
- explicit Fire(GWidget* parent = nullptr);
- RefPtr<GraphicsBitmap> bitmap;
- GLabel* stats;
- virtual void paint_event(GPaintEvent&) override;
- virtual void timer_event(Core::TimerEvent&) override;
- virtual void mousedown_event(GMouseEvent& event) override;
- virtual void mousemove_event(GMouseEvent& event) override;
- virtual void mouseup_event(GMouseEvent& event) override;
- bool dragging;
- int timeAvg;
- int cycles;
- int phase;
- };
- Fire::Fire(GWidget* parent)
- : GWidget(parent)
- {
- bitmap = GraphicsBitmap::create(GraphicsBitmap::Format::Indexed8, { 320, 200 });
- /* Initialize fire palette */
- for (int i = 0; i < 30; i++)
- bitmap->set_palette_color(i, s_palette[i]);
- /* Set remaining entries to white */
- for (int i = 30; i < 256; i++)
- bitmap->set_palette_color(i, Color::White);
- dragging = false;
- timeAvg = 0;
- cycles = 0;
- phase = 0;
- my_srand(time(nullptr));
- stop_timer();
- start_timer(20);
- /* Draw fire "source" on bottom row of pixels */
- for (int i = 0; i < FIRE_WIDTH; i++)
- bitmap->bits(bitmap->height() - 1)[i] = FIRE_MAX;
- /* Set off initital paint event */
- //update();
- }
- Fire::~Fire()
- {
- }
- void Fire::paint_event(GPaintEvent& event)
- {
- Core::ElapsedTimer timer;
- timer.start();
- GPainter painter(*this);
- painter.add_clip_rect(event.rect());
- /* Blit it! */
- painter.draw_scaled_bitmap(event.rect(), *bitmap, bitmap->rect());
- timeAvg += timer.elapsed();
- cycles++;
- }
- void Fire::timer_event(Core::TimerEvent&)
- {
- /* Update only even or odd columns per frame... */
- phase++;
- if (phase > 1)
- phase = 0;
- /* Paint our palettized buffer to screen */
- for (int px = 0 + phase; px < FIRE_WIDTH; px += 2) {
- for (int py = 1; py < 200; py++) {
- int rnd = my_rand() % 3;
- /* Calculate new pixel value, don't go below 0 */
- u8 nv = bitmap->bits(py)[px];
- if (nv > 0)
- nv -= (rnd & 1);
- /* ...sigh... */
- int epx = px + (1 - rnd);
- if (epx < 0)
- epx = 0;
- else if (epx > FIRE_WIDTH)
- epx = FIRE_WIDTH;
- bitmap->bits(py - 1)[epx] = nv;
- }
- }
- if ((cycles % 50) == 0) {
- dbgprintf("%d total cycles. finished 50 in %d ms, avg %d ms\n", cycles, timeAvg, timeAvg / 50);
- stats->set_text(String::format("%d ms", timeAvg / 50));
- timeAvg = 0;
- }
- update();
- }
- /*
- * Mouse handling events
- */
- void Fire::mousedown_event(GMouseEvent& event)
- {
- if (event.button() == GMouseButton::Left)
- dragging = true;
- return GWidget::mousedown_event(event);
- }
- /* FIXME: needs to account for the size of the window rect */
- void Fire::mousemove_event(GMouseEvent& event)
- {
- if (dragging) {
- if (event.y() >= 2 && event.y() < 398 && event.x() <= 638) {
- int ypos = event.y() / 2;
- int xpos = event.x() / 2;
- bitmap->bits(ypos - 1)[xpos] = FIRE_MAX + 5;
- bitmap->bits(ypos - 1)[xpos + 1] = FIRE_MAX + 5;
- bitmap->bits(ypos)[xpos] = FIRE_MAX + 5;
- bitmap->bits(ypos)[xpos + 1] = FIRE_MAX + 5;
- }
- }
- return GWidget::mousemove_event(event);
- }
- void Fire::mouseup_event(GMouseEvent& event)
- {
- if (event.button() == GMouseButton::Left)
- dragging = false;
- return GWidget::mouseup_event(event);
- }
- /*
- * Main
- */
- int main(int argc, char** argv)
- {
- GApplication app(argc, argv);
- auto window = GWindow::construct();
- window->set_double_buffering_enabled(false);
- window->set_title("Fire");
- window->set_resizable(false);
- window->set_rect(100, 100, 640, 400);
- auto fire = Fire::construct();
- window->set_main_widget(fire);
- auto time = GLabel::construct(fire);
- time->set_relative_rect({ 0, 4, 40, 10 });
- time->move_by({ window->width() - time->width(), 0 });
- time->set_foreground_color(Color::from_rgb(0x444444));
- fire->set_stat_label(time);
- window->show();
- window->set_icon(load_png("/res/icons/16x16/app-demo.png"));
- return app.exec();
- }
|