Revamped and streamlined retina / high-DPI support (resolves issue #1122).

With the highdpi window flag enabled on a retina-capable display and OS, content should now appear to the user at the same size and in the same positions as with the flag disabled.

As a result, mouse and touch coordinates, Texture and graphics dimensions, and the graphics coordinate system now use pixel density-scaled units instead of pixels. Raw pixel units should generally only be used for things such as shader algorithms which execute per-pixel and rely on accurate pixel dimensions. love.window.fromPixels and friends typically don’t need to be used anymore.

Images, Canvases, and Fonts can have an optional explicit ‘pixel density’ set when creating them. This allows for easily loading high pixel density content which displays at the same size as regular or low pixel density content.

API changes:

- Added Texture:getPixelWidth/getPixelHeight/getPixelDimensions and Texture:getPixelDensity. Texture:getWidth/getHeight return the pixel density-scaled width and height (as it will appear on the screen when drawn) rather than the number of pixels on each texture dimension.

- Added love.graphics.getPixelWidth/getPixelHeight/getPixelDimensions.

- Added optional ‘pixeldensity’ field to the settings table parameter of love.graphics.newImage. It defaults to 1, or if the file the Image was loaded from has “@2x”, “@3x”, etc. at the end of its name, it uses that number as the pixel density scale by default.

- love.graphics.newCanvas now takes a table as its third parameter, with fields “format”, “msaa”, and “pixeldensity”. pixeldensity defaults to the main screen’s pixel density. The width and height parameters specify the visual size that the Canvas will be drawn at / can be drawn to (pixel density-scaled units).

- love.graphics.newVideo accepts a table as its second parameter, with optional fields “audio” and “pixeldensity”. pixeldensity defaults to 1.

- love.graphics.newFont variants have an optional pixeldensity parameter at the end of the argument list. For TrueType fonts this defaults to the current pixel density scale of the screen, and for BMFonts and ImageFonts this defaults to 1.

- Added Font:getPixelDensity.

- Renamed love.window.getPixelScale to love.window.getPixelDensity.

--HG--
branch : minor
This commit is contained in:
Alex Szpakowski
2016-12-29 23:55:51 -04:00
parent 8aa6725039
commit d26002f5cc
53 changed files with 870 additions and 531 deletions
+14
View File
@@ -194,6 +194,20 @@ int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValu
return retval;
}
double luax_numberflag(lua_State *L, int table_index, const char *key, double defaultValue)
{
lua_getfield(L, table_index, key);
int retval;
if (!lua_isnumber(L, -1))
retval = defaultValue;
else
retval = lua_tonumber(L, -1);
lua_pop(L, 1);
return retval;
}
int luax_assert_argc(lua_State *L, int min)
{
int argc = lua_gettop(L);
+1
View File
@@ -161,6 +161,7 @@ void luax_pushstring(lua_State *L, const std::string &str);
bool luax_boolflag(lua_State *L, int table_index, const char *key, bool defaultValue);
int luax_intflag(lua_State *L, int table_index, const char *key, int defaultValue);
double luax_numberflag(lua_State *L, int table_index, const char *key, double defaultValue);
/**
* Convert the value at the specified index to an Lua number, and then
+42 -25
View File
@@ -44,26 +44,30 @@ namespace sdl
// SDL reports mouse coordinates in the window coordinate system in OS X, but
// we want them in pixel coordinates (may be different with high-DPI enabled.)
static void windowToPixelCoords(double *x, double *y)
static void windowToDPICoords(double *x, double *y)
{
auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
if (window)
window->windowToPixelCoords(x, y);
window->windowToDPICoords(x, y);
}
#ifndef LOVE_MACOSX
static void normalizedToPixelCoords(double *x, double *y)
static void normalizedToDPICoords(double *x, double *y)
{
auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
int w = 1, h = 1;
double w = 1.0, h = 1.0;
auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
if (window)
window->getPixelDimensions(w, h);
{
w = window->getWidth();
h = window->getHeight();
window->windowToDPICoords(&w, &h);
}
if (x)
*x = ((*x) * (double) w);
*x = ((*x) * w);
if (y)
*y = ((*y) * (double) h);
*y = ((*y) * h);
}
#endif
@@ -249,8 +253,8 @@ Message *Event::convert(const SDL_Event &e)
double y = (double) e.motion.y;
double xrel = (double) e.motion.xrel;
double yrel = (double) e.motion.yrel;
windowToPixelCoords(&x, &y);
windowToPixelCoords(&xrel, &yrel);
windowToDPICoords(&x, &y);
windowToDPICoords(&xrel, &yrel);
vargs.emplace_back(x);
vargs.emplace_back(y);
vargs.emplace_back(xrel);
@@ -276,7 +280,7 @@ Message *Event::convert(const SDL_Event &e)
double px = (double) e.button.x;
double py = (double) e.button.y;
windowToPixelCoords(&px, &py);
windowToDPICoords(&px, &py);
vargs.emplace_back(px);
vargs.emplace_back(py);
vargs.emplace_back((double) button);
@@ -313,15 +317,15 @@ Message *Event::convert(const SDL_Event &e)
if (touchNormalizationBug || fabs(touchinfo.x) >= 1.5 || fabs(touchinfo.y) >= 1.5 || fabs(touchinfo.dx) >= 1.5 || fabs(touchinfo.dy) >= 1.5)
{
touchNormalizationBug = true;
windowToPixelCoords(&touchinfo.x, &touchinfo.y);
windowToPixelCoords(&touchinfo.dx, &touchinfo.dy);
windowToDPICoords(&touchinfo.x, &touchinfo.y);
windowToDPICoords(&touchinfo.dx, &touchinfo.dy);
}
else
#endif
{
// SDL's coords are normalized to [0, 1], but we want them in pixels.
normalizedToPixelCoords(&touchinfo.x, &touchinfo.y);
normalizedToPixelCoords(&touchinfo.dx, &touchinfo.dy);
// SDL's coords are normalized to [0, 1], but we want screen coords.
normalizedToDPICoords(&touchinfo.x, &touchinfo.y);
normalizedToDPICoords(&touchinfo.dx, &touchinfo.dy);
}
// We need to update the love.touch.sdl internal state from here.
@@ -536,6 +540,7 @@ Message *Event::convertWindowEvent(const SDL_Event &e)
vargs.reserve(4);
window::Window *win = nullptr;
graphics::Graphics *gfx = nullptr;
if (e.type != SDL_WINDOWEVENT)
return nullptr;
@@ -559,17 +564,29 @@ Message *Event::convertWindowEvent(const SDL_Event &e)
break;
case SDL_WINDOWEVENT_RESIZED:
{
int px_w = e.window.data1;
int px_h = e.window.data2;
double width = e.window.data1;
double height = e.window.data2;
SDL_Window *sdlwin = SDL_GetWindowFromID(e.window.windowID);
if (sdlwin)
SDL_GL_GetDrawableSize(sdlwin, &px_w, &px_h);
gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
win = Module::getInstance<window::Window>(Module::M_WINDOW);
vargs.emplace_back((double) px_w);
vargs.emplace_back((double) px_h);
vargs.emplace_back((double) e.window.data1);
vargs.emplace_back((double) e.window.data2);
// WINDOWEVENT_SIZE_CHANGED will always occur before RESIZED.
// The size values in the Window aren't necessarily the same as the
// graphics size, which is what we want to output.
if (gfx)
{
width = gfx->getWidth();
height = gfx->getHeight();
}
else if (win)
{
width = win->getWidth();
height = win->getHeight();
windowToDPICoords(&width, &height);
}
vargs.emplace_back(width);
vargs.emplace_back(height);
msg = new Message("resize", vargs);
}
break;
+15 -3
View File
@@ -45,8 +45,15 @@ FileData::FileData(uint64 size, const std::string &filename)
throw love::Exception("Out of memory.");
}
if (filename.rfind('.') != std::string::npos)
extension = filename.substr(filename.rfind('.')+1);
size_t dotpos = filename.rfind('.');
if (dotpos != std::string::npos)
{
extension = filename.substr(dotpos + 1);
name = filename.substr(0, dotpos);
}
else
name = filename;
}
FileData::~FileData()
@@ -56,7 +63,7 @@ FileData::~FileData()
void *FileData::getData() const
{
return (void *)data;
return data;
}
size_t FileData::getSize() const
@@ -75,5 +82,10 @@ const std::string &FileData::getExtension() const
return extension;
}
const std::string &FileData::getName() const
{
return name;
}
} // filesystem
} // love
+4
View File
@@ -49,6 +49,7 @@ public:
const std::string &getFilename() const;
const std::string &getExtension() const;
const std::string &getName() const;
private:
@@ -64,6 +65,9 @@ private:
// The extension (without dot). Used to identify file type.
std::string extension;
// The file name without the extension (and without the dot).
std::string name;
}; // FileData
} // filesystem
+3 -1
View File
@@ -131,11 +131,13 @@ std::string BMFontLine::getAttributeString(const char *name) const
} // anonymous namespace
BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist)
BMFontRasterizer::BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist, float pixeldensity)
: fontSize(0)
, unicode(false)
, lineHeight(0)
{
this->pixelDensity = pixeldensity;
const std::string &filename = fontdef->getFilename();
size_t separatorpos = filename.rfind('/');
+1 -1
View File
@@ -42,7 +42,7 @@ class BMFontRasterizer : public Rasterizer
{
public:
BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist);
BMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &imagelist, float pixeldensity);
virtual ~BMFontRasterizer();
// Implements Rasterizer.
+12 -6
View File
@@ -47,12 +47,18 @@ Rasterizer *Font::newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hi
return newTrueTypeRasterizer(data.get(), size, hinting);
}
Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images)
Rasterizer *Font::newTrueTypeRasterizer(int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting)
{
return new BMFontRasterizer(fontdef, images);
StrongRef<DefaultFontData> data(new DefaultFontData, Acquire::NORETAIN);
return newTrueTypeRasterizer(data.get(), size, pixeldensity, hinting);
}
Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::string &text, int extraspacing)
Rasterizer *Font::newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float pixeldensity)
{
return new BMFontRasterizer(fontdef, images, pixeldensity);
}
Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::string &text, int extraspacing, float pixeldensity)
{
std::vector<uint32> glyphs;
glyphs.reserve(text.size());
@@ -70,12 +76,12 @@ Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, const std::st
throw love::Exception("UTF-8 decoding error: %s", e.what());
}
return newImageRasterizer(data, &glyphs[0], (int) glyphs.size(), extraspacing);
return newImageRasterizer(data, &glyphs[0], (int) glyphs.size(), extraspacing, pixeldensity);
}
Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing)
Rasterizer *Font::newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity)
{
return new ImageRasterizer(data, glyphs, numglyphs, extraspacing);
return new ImageRasterizer(data, glyphs, numglyphs, extraspacing, pixeldensity);
}
GlyphData *Font::newGlyphData(Rasterizer *r, const std::string &text)
+5 -3
View File
@@ -48,12 +48,14 @@ public:
virtual Rasterizer *newRasterizer(love::filesystem::FileData *data) = 0;
virtual Rasterizer *newTrueTypeRasterizer(int size, TrueTypeRasterizer::Hinting hinting);
virtual Rasterizer *newTrueTypeRasterizer(int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting);
virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) = 0;
virtual Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting) = 0;
virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images);
virtual Rasterizer *newBMFontRasterizer(love::filesystem::FileData *fontdef, const std::vector<image::ImageData *> &images, float pixeldensity);
virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, const std::string &glyphs, int extraspacing);
virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int length, int extraspacing);
virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, const std::string &glyphs, int extraspacing, float pixeldensity);
virtual Rasterizer *newImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int length, int extraspacing, float pixeldensity);
virtual GlyphData *newGlyphData(Rasterizer *r, const std::string &glyph);
virtual GlyphData *newGlyphData(Rasterizer *r, uint32 glyph);
+3 -1
View File
@@ -34,12 +34,14 @@ inline bool equal(const love::image::pixel &a, const love::image::pixel &b)
return (a.r == b.r && a.g == b.g && a.b == b.b && a.a == b.a);
}
ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing)
ImageRasterizer::ImageRasterizer(love::image::ImageData *data, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity)
: imageData(data)
, glyphs(glyphs)
, numglyphs(numglyphs)
, extraSpacing(extraspacing)
{
this->pixelDensity = pixeldensity;
if (data->getFormat() != PIXELFORMAT_RGBA8)
throw love::Exception("Only 32-bit RGBA images are supported in Image Fonts!");
+1 -1
View File
@@ -39,7 +39,7 @@ namespace font
class ImageRasterizer : public Rasterizer
{
public:
ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing);
ImageRasterizer(love::image::ImageData *imageData, uint32 *glyphs, int numglyphs, int extraspacing, float pixeldensity);
virtual ~ImageRasterizer();
// Implement Rasterizer
+5
View File
@@ -102,5 +102,10 @@ float Rasterizer::getKerning(uint32 /*leftglyph*/, uint32 /*rightglyph*/) const
return 0.0f;
}
float Rasterizer::getPixelDensity() const
{
return pixelDensity;
}
} // font
} // love
+3
View File
@@ -120,9 +120,12 @@ public:
virtual DataType getDataType() const = 0;
float getPixelDensity() const;
protected:
FontMetrics metrics;
float pixelDensity;
}; // Rasterizer
+15 -2
View File
@@ -20,9 +20,12 @@
#include "Font.h"
// LOVE
#include "TrueTypeRasterizer.h"
#include "font/BMFontRasterizer.h"
#include "window/Window.h"
// C++
#include <string.h>
namespace love
@@ -48,14 +51,24 @@ Rasterizer *Font::newRasterizer(love::filesystem::FileData *data)
if (TrueTypeRasterizer::accepts(library, data))
return newTrueTypeRasterizer(data, 12, TrueTypeRasterizer::HINTING_NORMAL);
else if (BMFontRasterizer::accepts(data))
return newBMFontRasterizer(data, {});
return newBMFontRasterizer(data, {}, 1.0f);
throw love::Exception("Invalid font file: %s", data->getFilename().c_str());
}
Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting)
{
return new TrueTypeRasterizer(library, data, size, hinting);
float pixeldensity = 1.0f;
auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
if (window != nullptr)
pixeldensity = window->getPixelDensity();
return newTrueTypeRasterizer(data, size, pixeldensity, hinting);
}
Rasterizer *Font::newTrueTypeRasterizer(love::Data *data, int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting)
{
return new TrueTypeRasterizer(library, data, size, pixeldensity, hinting);
}
const char *Font::getName() const
+4 -3
View File
@@ -44,11 +44,12 @@ public:
virtual ~Font();
// Implements Font
Rasterizer *newRasterizer(love::filesystem::FileData *data);
Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting);
Rasterizer *newRasterizer(love::filesystem::FileData *data) override;
Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, TrueTypeRasterizer::Hinting hinting) override;
Rasterizer *newTrueTypeRasterizer(love::Data *data, int size, float pixeldensity, TrueTypeRasterizer::Hinting hinting) override;
// Implement Module
const char *getName() const;
const char *getName() const override;
private:
@@ -20,9 +20,11 @@
// LOVE
#include "TrueTypeRasterizer.h"
#include "common/Exception.h"
// C
#include <math.h>
namespace love
{
namespace font
@@ -30,10 +32,13 @@ namespace font
namespace freetype
{
TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, love::Data *data, int size, Hinting hinting)
TrueTypeRasterizer::TrueTypeRasterizer(FT_Library library, love::Data *data, int size, float pixeldensity, Hinting hinting)
: data(data)
, hinting(hinting)
{
this->pixelDensity = pixeldensity;
size = floorf(size * pixeldensity + 0.5f);
if (size <= 0)
throw love::Exception("Invalid TrueType font size: %d", size);
@@ -44,7 +44,7 @@ class TrueTypeRasterizer : public love::font::TrueTypeRasterizer
{
public:
TrueTypeRasterizer(FT_Library library, love::Data *data, int size, Hinting hinting);
TrueTypeRasterizer(FT_Library library, love::Data *data, int size, float pixeldensity, Hinting hinting);
virtual ~TrueTypeRasterizer();
// Implement Rasterizer
+30 -14
View File
@@ -78,7 +78,13 @@ int w_newTrueTypeRasterizer(lua_State *L)
if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
return luaL_error(L, "Invalid TrueType font hinting mode: %s", hintstr);
luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, hinting); });
if (lua_isnoneornil(L, 3))
luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, hinting); });
else
{
float pixeldensity = (float) luaL_checknumber(L, 3);
luax_catchexcept(L, [&](){ t = instance()->newTrueTypeRasterizer(size, pixeldensity, hinting); });
}
}
else
{
@@ -98,10 +104,21 @@ int w_newTrueTypeRasterizer(lua_State *L)
if (hintstr && !TrueTypeRasterizer::getConstant(hintstr, hinting))
return luaL_error(L, "Invalid TrueType font hinting mode: %s", hintstr);
luax_catchexcept(L,
[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
[&](bool) { d->release(); }
);
if (lua_isnoneornil(L, 4))
{
luax_catchexcept(L,
[&]() { t = instance()->newTrueTypeRasterizer(d, size, hinting); },
[&](bool) { d->release(); }
);
}
else
{
float pixeldensity = (float) luaL_checknumber(L, 4);
luax_catchexcept(L,
[&]() { t = instance()->newTrueTypeRasterizer(d, size, pixeldensity, hinting); },
[&](bool) { d->release(); }
);
}
}
luax_pushtype(L, t);
@@ -121,6 +138,7 @@ int w_newBMFontRasterizer(lua_State *L)
filesystem::FileData *d = filesystem::luax_getfiledata(L, 1);
std::vector<image::ImageData *> images;
float pixeldensity = (float) luaL_optnumber(L, 3, 1.0);
if (lua_istable(L, 2))
{
@@ -138,17 +156,14 @@ int w_newBMFontRasterizer(lua_State *L)
}
else
{
for (int i = 2; i <= lua_gettop(L); i++)
{
convimagedata(L, i);
image::ImageData *id = luax_checktype<image::ImageData>(L, i);
images.push_back(id);
id->retain();
}
convimagedata(L, 2);
image::ImageData *id = luax_checktype<image::ImageData>(L, 2);
images.push_back(id);
id->retain();
}
luax_catchexcept(L,
[&]() { t = instance()->newBMFontRasterizer(d, images); },
[&]() { t = instance()->newBMFontRasterizer(d, images, pixeldensity); },
[&](bool) { d->release(); for (auto id : images) id->release(); }
);
@@ -166,8 +181,9 @@ int w_newImageRasterizer(lua_State *L)
image::ImageData *d = luax_checktype<image::ImageData>(L, 1);
std::string glyphs = luax_checkstring(L, 2);
int extraspacing = (int) luaL_optnumber(L, 3, 0);
float pixeldensity = (float) luaL_optnumber(L, 4, 1.0);
luax_catchexcept(L, [&](){ t = instance()->newImageRasterizer(d, glyphs, extraspacing); });
luax_catchexcept(L, [&](){ t = instance()->newImageRasterizer(d, glyphs, extraspacing, pixeldensity); });
luax_pushtype(L, t);
t->release();
+5 -2
View File
@@ -290,14 +290,14 @@ public:
/**
* Sets the current graphics display viewport dimensions.
**/
virtual void setViewportSize(int width, int height) = 0;
virtual void setViewportSize(int width, int height, int pixelwidth, int pixelheight) = 0;
/**
* Sets the current graphics display viewport and initializes the renderer.
* @param width The viewport width.
* @param height The viewport height.
**/
virtual bool setMode(int width, int height) = 0;
virtual bool setMode(int width, int height, int pixelwidth, int pixelheight) = 0;
/**
* Un-sets the current graphics display mode (uninitializing objects if
@@ -319,6 +319,9 @@ public:
**/
virtual bool isActive() const = 0;
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
virtual bool isCanvasActive() const = 0;
virtual void flushStreamDraws() = 0;
+13 -12
View File
@@ -46,12 +46,13 @@ Quad::~Quad()
void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
{
viewport = v;
this->sw = sw;
this->sh = sh;
// Vertices are ordered for use with triangle strips:
// 0----2
// | / |
// | / |
// 1----3
// 0---2
// | / |
// 1---3
vertices[0].x = 0.0f;
vertices[0].y = 0.0f;
vertices[1].x = 0.0f;
@@ -61,14 +62,14 @@ void Quad::refresh(const Quad::Viewport &v, double sw, double sh)
vertices[3].x = (float) v.w;
vertices[3].y = (float) v.h;
vertices[0].s = (float) (v.x/sw);
vertices[0].t = (float) (v.y/sh);
vertices[1].s = (float) (v.x/sw);
vertices[1].t = (float) ((v.y+v.h)/sh);
vertices[2].s = (float) ((v.x+v.w)/sw);
vertices[2].t = (float) (v.y/sh);
vertices[3].s = (float) ((v.x+v.w)/sw);
vertices[3].t = (float) ((v.y+v.h)/sh);
vertices[0].s = (float) (v.x / sw);
vertices[0].t = (float) (v.y / sh);
vertices[1].s = (float) (v.x / sw);
vertices[1].t = (float) ((v.y + v.h) / sh);
vertices[2].s = (float) ((v.x + v.w) / sw);
vertices[2].t = (float) (v.y / sh);
vertices[3].s = (float) ((v.x + v.w) / sw);
vertices[3].t = (float) ((v.y + v.h) / sh);
}
void Quad::setViewport(const Quad::Viewport &v)
+17
View File
@@ -33,6 +33,8 @@ Texture::Texture()
: format(PIXELFORMAT_UNKNOWN)
, width(0)
, height(0)
, pixelWidth(0)
, pixelHeight(0)
, filter(getDefaultFilter())
, wrap()
, vertices()
@@ -90,6 +92,21 @@ int Texture::getHeight() const
return height;
}
int Texture::getPixelWidth() const
{
return pixelWidth;
}
int Texture::getPixelHeight() const
{
return pixelHeight;
}
float Texture::getPixelDensity() const
{
return (float) pixelHeight / (float) height;
}
const Texture::Filter &Texture::getFilter() const
{
return filter;
+8
View File
@@ -94,6 +94,11 @@ public:
virtual int getWidth() const;
virtual int getHeight() const;
virtual int getPixelWidth() const;
virtual int getPixelHeight() const;
float getPixelDensity() const;
virtual void setFilter(const Filter &f) = 0;
virtual const Filter &getFilter() const;
@@ -125,6 +130,9 @@ protected:
int width;
int height;
int pixelWidth;
int pixelHeight;
Filter filter;
Wrap wrap;
+22 -25
View File
@@ -100,35 +100,32 @@ static bool createMSAABuffer(int width, int height, int &samples, PixelFormat pi
love::Type Canvas::type("Canvas", &Texture::type);
int Canvas::canvasCount = 0;
Canvas::Canvas(int width, int height, PixelFormat format, int msaa)
: fbo(0)
Canvas::Canvas(int width, int height, const Settings &settings)
: settings(settings)
, fbo(0)
, texture(0)
, msaa_buffer(0)
, requested_format(format)
, requested_samples(msaa)
, actual_samples(0)
, texture_memory(0)
{
this->width = width;
this->height = height;
float w = static_cast<float>(width);
float h = static_cast<float>(height);
this->pixelWidth = (int) ((width * settings.pixeldensity) + 0.5);
this->pixelHeight = (int) ((height * settings.pixeldensity) + 0.5);
// Vertices are ordered for use with triangle strips:
// 0----2
// | / |
// | / |
// 1----3
// 0---2
// | / |
// 1---3
// world coordinates
vertices[0].x = 0;
vertices[0].y = 0;
vertices[1].x = 0;
vertices[1].y = h;
vertices[2].x = w;
vertices[1].y = (float) height;
vertices[2].x = (float) width;
vertices[2].y = 0;
vertices[3].x = w;
vertices[3].y = h;
vertices[3].x = (float) width;
vertices[3].y = (float) height;
// texture coordinates
vertices[0].s = 0;
@@ -140,7 +137,7 @@ Canvas::Canvas(int width, int height, PixelFormat format, int msaa)
vertices[3].s = 1;
vertices[3].t = 1;
this->format = getSizedFormat(requested_format);
this->format = getSizedFormat(settings.format);
loadVolatile();
@@ -165,7 +162,7 @@ bool Canvas::loadVolatile()
status = GL_FRAMEBUFFER_COMPLETE;
// glTexImage2D is guaranteed to error in this case.
if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
{
status = GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT;
return false;
@@ -173,8 +170,8 @@ bool Canvas::loadVolatile()
// getMaxRenderbufferSamples will be 0 on systems that don't support
// multisampled renderbuffers / don't export FBO multisample extensions.
requested_samples = std::min(requested_samples, gl.getMaxRenderbufferSamples());
requested_samples = std::max(requested_samples, 0);
settings.msaa = std::min(settings.msaa, gl.getMaxRenderbufferSamples());
settings.msaa = std::max(settings.msaa, 0);
glGenTextures(1, &texture);
gl.bindTextureToUnit(texture, 0, false);
@@ -191,8 +188,8 @@ bool Canvas::loadVolatile()
while (glGetError() != GL_NO_ERROR)
/* Clear the error buffer. */;
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, width, height, 0,
fmt.externalformat, fmt.type, nullptr);
glTexImage2D(GL_TEXTURE_2D, 0, fmt.internalformat, pixelWidth, pixelHeight,
0, fmt.externalformat, fmt.type, nullptr);
if (glGetError() != GL_NO_ERROR)
{
@@ -215,14 +212,14 @@ bool Canvas::loadVolatile()
return false;
}
actual_samples = requested_samples == 1 ? 0 : requested_samples;
actual_samples = settings.msaa == 1 ? 0 : settings.msaa;
if (actual_samples > 0 && !createMSAABuffer(width, height, actual_samples, format, msaa_buffer))
actual_samples = 0;
size_t prevmemsize = texture_memory;
texture_memory = getPixelFormatSize(format) * width * height;
texture_memory = getPixelFormatSize(format) * pixelWidth * pixelHeight;
if (msaa_buffer != 0)
texture_memory += (texture_memory * actual_samples);
@@ -276,7 +273,7 @@ bool Canvas::setWrap(const Texture::Wrap &w)
wrap = w;
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
&& (width != nextP2(width) || height != nextP2(height)))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
{
if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
success = false;
@@ -306,7 +303,7 @@ ptrdiff_t Canvas::getHandle() const
love::image::ImageData *Canvas::newImageData(love::image::Image *module, int x, int y, int w, int h)
{
if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getWidth() || (y + h) > getHeight())
if (x < 0 || y < 0 || w <= 0 || h <= 0 || (x + w) > getPixelWidth() || (y + h) > getPixelHeight())
throw love::Exception("Invalid rectangle dimensions.");
Graphics *gfx = Module::getInstance<Graphics>(Module::M_GRAPHICS);
+11 -5
View File
@@ -45,7 +45,14 @@ public:
static love::Type type;
Canvas(int width, int height, PixelFormat format = PIXELFORMAT_NORMAL, int msaa = 0);
struct Settings
{
PixelFormat format = PIXELFORMAT_NORMAL;
float pixeldensity = 1.0f;
int msaa = 0;
};
Canvas(int width, int height, const Settings &settings);
virtual ~Canvas();
// Implements Volatile.
@@ -71,7 +78,7 @@ public:
inline int getRequestedMSAA() const
{
return requested_samples;
return settings.msaa;
}
inline ptrdiff_t getMSAAHandle() const
@@ -95,16 +102,15 @@ private:
void drawv(Graphics *gfx, const Matrix4 &t, const Vertex *v) override;
Settings settings;
GLuint fbo;
GLuint texture;
GLuint msaa_buffer;
PixelFormat requested_format;
GLenum status;
int requested_samples;
int actual_samples;
size_t texture_memory;
+50 -38
View File
@@ -47,19 +47,20 @@ static inline uint16 normToUint16(double n)
love::Type Font::type("Font", &Object::type);
int Font::fontCount = 0;
Font::Font(love::font::Rasterizer *r, const Texture::Filter &filter)
Font::Font(love::font::Rasterizer *r, const Texture::Filter &f)
: rasterizers({r})
, height(r->getHeight())
, lineHeight(1)
, textureWidth(128)
, textureHeight(128)
, filter(filter)
, filter(f)
, pixelDensity(r->getPixelDensity())
, useSpacesAsTab(false)
, quadIndices(20) // We make this bigger at draw-time, if needed.
, textureCacheID(0)
, textureMemorySize(0)
{
this->filter.mipmap = Texture::FILTER_NONE;
filter.mipmap = Texture::FILTER_NONE;
// Try to find the best texture size match for the font size. default to the
// largest texture size if no rough match is found.
@@ -100,9 +101,10 @@ Font::TextureSize Font::getNextTextureSize() const
{
TextureSize size = {textureWidth, textureHeight};
int maxsize = std::min(4096, gl.getMaxTextureSize());
int maxwidth = std::min(8192, gl.getMaxTextureSize());
int maxheight = std::min(4096, gl.getMaxTextureSize());
if (size.width * 2 <= maxsize || size.height * 2 <= maxsize)
if (size.width * 2 <= maxwidth || size.height * 2 <= maxheight)
{
// {128, 128} -> {256, 128} -> {256, 256} -> {512, 256} -> etc.
if (size.width == size.height)
@@ -114,7 +116,7 @@ Font::TextureSize Font::getNextTextureSize() const
return size;
}
void Font::createTexture()
bool Font::createTexture()
{
auto gfx = Module::getInstance<graphics::Graphics>(Module::M_GRAPHICS);
gfx->flushStreamDraws();
@@ -200,6 +202,8 @@ void Font::createTexture()
}
else
textures.push_back(t);
return true;
}
love::font::GlyphData *Font::getRasterizerGlyphData(uint32 glyph)
@@ -236,32 +240,35 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
int w = gd->getWidth();
int h = gd->getHeight();
if (textureX + w + TEXTURE_PADDING > textureWidth)
if (w + TEXTURE_PADDING * 2 < textureWidth && h + TEXTURE_PADDING * 2 < textureHeight)
{
// out of space - new row!
textureX = TEXTURE_PADDING;
textureY += rowHeight;
rowHeight = TEXTURE_PADDING;
}
if (textureX + w + TEXTURE_PADDING > textureWidth)
{
// Out of space - new row!
textureX = TEXTURE_PADDING;
textureY += rowHeight;
rowHeight = TEXTURE_PADDING;
}
if (textureY + h + TEXTURE_PADDING > textureHeight)
{
// totally out of space - new texture!
createTexture();
if (textureY + h + TEXTURE_PADDING > textureHeight)
{
// Totally out of space - new texture!
createTexture();
// Makes sure the above code for checking if the glyph can fit at
// the current position in the texture is run again for this glyph.
return addGlyph(glyph);
// Makes sure the above code for checking if the glyph can fit at
// the current position in the texture is run again for this glyph.
return addGlyph(glyph);
}
}
Glyph g;
g.texture = 0;
g.spacing = gd->getAdvance();
g.spacing = floorf(gd->getAdvance() / pixelDensity + 0.5f);
memset(g.vertices, 0, sizeof(GlyphVertex) * 4);
// don't waste space for empty glyphs. also fixes a divide by zero bug with ATI drivers
// Don't waste space for empty glyphs.
if (w > 0 && h > 0)
{
bool isSRGB = isGammaCorrect();
@@ -278,31 +285,31 @@ const Font::Glyph &Font::addGlyph(uint32 glyph)
Color c(255, 255, 255, 255);
// 0----2
// | / |
// | / |
// 1----3
const GlyphVertex verts[4] = {
{float(0), float(0), normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
{float(0), float(h), normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
{float(w), float(0), normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
{float(w), float(h), normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
// 0---2
// | / |
// 1---3
const GlyphVertex verts[4] =
{
{0.0f, 0.0f, normToUint16((tX+0)/tWidth), normToUint16((tY+0)/tHeight), c},
{0.0f, h/pixelDensity, normToUint16((tX+0)/tWidth), normToUint16((tY+h)/tHeight), c},
{w/pixelDensity, 0.0f, normToUint16((tX+w)/tWidth), normToUint16((tY+0)/tHeight), c},
{w/pixelDensity, h/pixelDensity, normToUint16((tX+w)/tWidth), normToUint16((tY+h)/tHeight), c}
};
// Copy vertex data to the glyph and set proper bearing.
for (int i = 0; i < 4; i++)
{
g.vertices[i] = verts[i];
g.vertices[i].x += gd->getBearingX();
g.vertices[i].y -= gd->getBearingY();
g.vertices[i].x += gd->getBearingX() / pixelDensity;
g.vertices[i].y -= gd->getBearingY() / pixelDensity;
}
textureX += w + TEXTURE_PADDING;
rowHeight = std::max(rowHeight, h + TEXTURE_PADDING);
}
const auto p = glyphs.insert(std::make_pair(glyph, g));
return p.first->second;
glyphs[glyph] = g;
return glyphs[glyph];
}
const Font::Glyph &Font::findGlyph(uint32 glyph)
@@ -329,7 +336,7 @@ float Font::getKerning(uint32 leftglyph, uint32 rightglyph)
{
if (r->hasGlyph(leftglyph) && r->hasGlyph(rightglyph))
{
k = r->getKerning(leftglyph, rightglyph);
k = floorf(r->getKerning(leftglyph, rightglyph) / pixelDensity + 0.5f);
break;
}
}
@@ -390,7 +397,7 @@ void Font::getCodepointsFromString(const std::vector<ColoredString> &strs, Color
float Font::getHeight() const
{
return (float) height;
return (float) floorf(height / pixelDensity + 0.5f);
}
std::vector<Font::DrawCommand> Font::generateVertices(const ColoredCodepoints &codepoints, const Colorf &constantcolor, std::vector<GlyphVertex> &vertices, float extra_spacing, Vector offset, TextInfo *info)
@@ -945,12 +952,12 @@ void Font::unloadVolatile()
int Font::getAscent() const
{
return rasterizers[0]->getAscent();
return floorf(rasterizers[0]->getAscent() / pixelDensity + 0.5f);
}
int Font::getDescent() const
{
return rasterizers[0]->getDescent();
return floorf(rasterizers[0]->getDescent() / pixelDensity + 0.5f);
}
float Font::getBaseline() const
@@ -1014,6 +1021,11 @@ void Font::setFallbacks(const std::vector<Font *> &fallbacks)
rasterizers.push_back(f->rasterizers[0]);
}
float Font::getPixelDensity() const
{
return pixelDensity;
}
uint32 Font::getTextureCacheID() const
{
return textureCacheID;
+6 -2
View File
@@ -100,7 +100,7 @@ public:
int vertexcount;
};
Font(love::font::Rasterizer *r, const Texture::Filter &filter = Texture::getDefaultFilter());
Font(love::font::Rasterizer *r, const Texture::Filter &filter);
virtual ~Font();
@@ -179,6 +179,8 @@ public:
void setFallbacks(const std::vector<Font *> &fallbacks);
float getPixelDensity() const;
uint32 getTextureCacheID() const;
static bool getConstant(const char *in, AlignMode &out);
@@ -202,7 +204,7 @@ private:
};
TextureSize getNextTextureSize() const;
void createTexture();
bool createTexture();
love::font::GlyphData *getRasterizerGlyphData(uint32 glyph);
const Glyph &addGlyph(uint32 glyph);
const Glyph &findGlyph(uint32 glyph);
@@ -230,6 +232,8 @@ private:
Texture::Filter filter;
float pixelDensity;
int textureX, textureY;
int rowHeight;
-1
View File
@@ -366,7 +366,6 @@ void QuadIndices::fill()
using namespace love::graphics::vertex;
fillIndices(TriangleIndexMode::QUADS, 0, maxSize * 4, (T *) indices);
indexBuffer->fill(0, indexBuffer->getSize(), indices);
}
+89 -31
View File
@@ -56,6 +56,8 @@ Graphics::Graphics()
: quadIndices(nullptr)
, width(0)
, height(0)
, pixelWidth(0)
, pixelHeight(0)
, created(false)
, active(true)
, writingToStencil(false)
@@ -74,10 +76,11 @@ Graphics::Graphics()
if (window->isOpen())
{
int w = 0, h = 0;
window->getPixelDimensions(w, h);
double w = window->getWidth();
double h = window->getHeight();
window->windowToDPICoords(&w, &h);
setMode(w, h);
setMode((int) w, (int) h, window->getPixelWidth(), window->getPixelHeight());
}
}
}
@@ -221,22 +224,45 @@ void Graphics::checkSetDefaultFont()
states.back().font.set(defaultFont.get());
}
void Graphics::setViewportSize(int width, int height)
double Graphics::getCurrentPixelDensity() const
{
if (states.back().canvases.size() > 0)
{
Canvas *c = states.back().canvases[0];
return (double) c->getPixelHeight() / (double) c->getHeight();
}
return getScreenPixelDensity();
}
double Graphics::getScreenPixelDensity() const
{
return (double) getPixelHeight() / (double) getHeight();
}
void Graphics::setViewportSize(int width, int height, int pixelwidth, int pixelheight)
{
this->width = width;
this->height = height;
this->pixelWidth = pixelwidth;
this->pixelHeight = pixelheight;
if (states.back().canvases.empty())
{
// Set the viewport to top-left corner.
gl.setViewport({0, 0, width, height}, false);
gl.setViewport({0, 0, pixelwidth, pixelheight});
// Re-apply the scissor if it was active, since the rectangle passed to
// glScissor is affected by the viewport dimensions.
if (states.back().scissor)
setScissor(states.back().scissorRect);
// Set up the projection matrix
projectionMatrix = Matrix4::ortho(0.0, (float) width, (float) height, 0.0);
}
}
bool Graphics::setMode(int width, int height)
bool Graphics::setMode(int width, int height, int pixelwidth, int pixelheight)
{
this->width = width;
this->height = height;
@@ -247,6 +273,8 @@ bool Graphics::setMode(int width, int height)
created = true;
setViewportSize(width, height, pixelwidth, pixelheight);
// Enable blending
glEnable(GL_BLEND);
@@ -310,8 +338,6 @@ bool Graphics::setMode(int width, int height)
if (quadIndices == nullptr)
quadIndices = new QuadIndices(20);
setViewportSize(width, height);
// Restore the graphics state.
restoreState(states.back());
@@ -608,13 +634,15 @@ void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
PixelFormat firstformat = firstcanvas->getPixelFormat();
bool hasSRGBcanvas = firstformat == PIXELFORMAT_sRGBA8;
int pixelwidth = firstcanvas->getPixelWidth();
int pixelheight = firstcanvas->getPixelHeight();
for (int i = 1; i < ncanvases; i++)
{
Canvas *c = canvases[i];
if (c->getWidth() != firstcanvas->getWidth() || c->getHeight() != firstcanvas->getHeight())
throw love::Exception("All canvases in must have the same dimensions.");
if (c->getPixelWidth() != pixelwidth || c->getPixelHeight() != pixelheight)
throw love::Exception("All canvases in must have the same pixel dimensions.");
if (!multiformatsupported && c->getPixelFormat() != firstformat)
throw love::Exception("This system doesn't support multi-canvas rendering with different canvas formats.");
@@ -632,10 +660,15 @@ void Graphics::setCanvas(const std::vector<Canvas *> &canvases)
bindCachedFBO(canvases);
gl.setViewport({0, 0, pixelwidth, pixelheight});
// Re-apply the scissor if it was active, since the rectangle passed to
// glScissor is affected by the viewport dimensions.
if (state.scissor)
setScissor(state.scissorRect);
int w = firstcanvas->getWidth();
int h = firstcanvas->getHeight();
gl.setViewport({0, 0, w, h}, true);
projectionMatrix = Matrix4::ortho(0.0, (float) w, 0.0, (float) h);
// Make sure the correct sRGB setting is used when drawing to the canvases.
@@ -683,7 +716,13 @@ void Graphics::setCanvas()
state.canvases.clear();
gl.bindFramebuffer(OpenGL::FRAMEBUFFER_ALL, gl.getDefaultFBO());
gl.setViewport({0, 0, width, height}, false);
gl.setViewport({0, 0, pixelWidth, pixelHeight});
// Re-apply the scissor if it was active, since the rectangle passed to
// glScissor is affected by the viewport dimensions.
if (state.scissor)
setScissor(state.scissorRect);
// The projection matrix is flipped compared to rendering to a canvas, due
// to OpenGL considering (0,0) bottom-left instead of top-left.
@@ -723,8 +762,8 @@ void Graphics::endPass()
// Resolve MSAA buffers.
if (canvases.size() > 0 && canvases[0]->getMSAA() > 1)
{
int w = canvases[0]->getWidth();
int h = canvases[0]->getHeight();
int w = canvases[0]->getPixelWidth();
int h = canvases[0]->getPixelHeight();
for (int i = 0; i < (int) canvases.size(); i++)
{
@@ -909,8 +948,8 @@ void Graphics::bindCachedFBO(const std::vector<Canvas *> &canvases)
}
else
{
int w = canvases[0]->getWidth();
int h = canvases[0]->getHeight();
int w = canvases[0]->getPixelWidth();
int h = canvases[0]->getPixelHeight();
int msaa = std::max(canvases[0]->getMSAA(), 1);
glGenFramebuffers(1, &fbo);
@@ -1054,8 +1093,8 @@ void Graphics::present(void *screenshotCallbackData)
if (!pendingScreenshotCallbacks.empty())
{
int w = getWidth();
int h = getHeight();
int w = getPixelWidth();
int h = getPixelHeight();
size_t row = 4 * w;
size_t size = row * h;
@@ -1170,6 +1209,16 @@ int Graphics::getHeight() const
return height;
}
int Graphics::getPixelWidth() const
{
return pixelWidth;
}
int Graphics::getPixelHeight() const
{
return pixelHeight;
}
bool Graphics::isCreated() const
{
return created;
@@ -1182,8 +1231,17 @@ void Graphics::setScissor(const Rect &rect)
DisplayState &state = states.back();
glEnable(GL_SCISSOR_TEST);
double density = getCurrentPixelDensity();
Rect glrect;
glrect.x = (int) (rect.x * density);
glrect.y = (int) (rect.y * density);
glrect.w = (int) (rect.w * density);
glrect.h = (int) (rect.h * density);
// OpenGL's reversed y-coordinate is compensated for in OpenGL::setScissor.
gl.setScissor(rect, !state.canvases.empty());
gl.setScissor(glrect, !state.canvases.empty());
state.scissor = true;
state.scissorRect = rect;
@@ -1358,14 +1416,14 @@ void Graphics::clearStencil()
glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
}
Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Flags &flags)
Image *Graphics::newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings)
{
return new Image(data, flags);
return new Image(data, settings);
}
Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Flags &flags)
Image *Graphics::newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings)
{
return new Image(cdata, flags);
return new Image(cdata, settings);
}
Quad *Graphics::newQuad(Quad::Viewport v, double sw, double sh)
@@ -1388,15 +1446,15 @@ ParticleSystem *Graphics::newParticleSystem(Texture *texture, int size)
return new ParticleSystem(texture, size);
}
Canvas *Graphics::newCanvas(int width, int height, PixelFormat format, int msaa)
Canvas *Graphics::newCanvas(int width, int height, const Canvas::Settings &settings)
{
if (!Canvas::isSupported())
throw love::Exception("Canvases are not supported by your OpenGL drivers!");
if (!Canvas::isFormatSupported(format))
if (!Canvas::isFormatSupported(settings.format))
{
const char *fstr = "rgba8";
love::getConstant(Canvas::getSizedFormat(format), fstr);
love::getConstant(Canvas::getSizedFormat(settings.format), fstr);
throw love::Exception("The %s canvas format is not supported by your OpenGL drivers.", fstr);
}
@@ -1405,7 +1463,7 @@ Canvas *Graphics::newCanvas(int width, int height, PixelFormat format, int msaa)
else if (height > gl.getMaxTextureSize())
throw Exception("Cannot create canvas: height of %d pixels is too large for this system.", height);
Canvas *canvas = new Canvas(width, height, format, msaa);
Canvas *canvas = new Canvas(width, height, settings);
GLenum err = canvas->getStatus();
// everything ok, return canvas (early out)
@@ -1447,9 +1505,9 @@ Text *Graphics::newText(Font *font, const std::vector<Font::ColoredString> &text
return new Text(font, text);
}
Video *Graphics::newVideo(love::video::VideoStream *stream)
Video *Graphics::newVideo(love::video::VideoStream *stream, float pixeldensity)
{
return new Video(stream);
return new Video(stream, pixeldensity);
}
bool Graphics::isGammaCorrect() const
@@ -1690,7 +1748,7 @@ void Graphics::setPointSize(float size)
if (streamBufferState.primitiveMode == vertex::PrimitiveMode::POINTS)
flushStreamDraws();
gl.setPointSize(size);
gl.setPointSize(size * getCurrentPixelDensity());
states.back().pointSize = size;
}
+15 -14
View File
@@ -80,8 +80,8 @@ public:
// Implements Module.
const char *getName() const override;
void setViewportSize(int width, int height) override;
bool setMode(int width, int height) override;
void setViewportSize(int width, int height, int pixelwidth, int pixelheight) override;
bool setMode(int width, int height, int pixelwidth, int pixelheight) override;
void unSetMode() override;
void setActive(bool active) override;
@@ -109,15 +109,13 @@ public:
**/
void present(void *screenshotCallbackData);
/**
* Gets the width of the current graphics viewport.
**/
int getWidth() const;
int getWidth() const override;
int getHeight() const override;
int getPixelWidth() const;
int getPixelHeight() const;
/**
* Gets the height of the current graphics viewport.
**/
int getHeight() const;
double getCurrentPixelDensity() const;
double getScreenPixelDensity() const;
/**
* True if a graphics viewport is set.
@@ -172,8 +170,8 @@ public:
/**
* Creates an Image object with padding and/or optimization.
**/
Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Flags &flags);
Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Flags &flags);
Image *newImage(const std::vector<love::image::ImageData *> &data, const Image::Settings &settings);
Image *newImage(const std::vector<love::image::CompressedImageData *> &cdata, const Image::Settings &settings);
Quad *newQuad(Quad::Viewport v, double sw, double sh);
@@ -186,7 +184,7 @@ public:
ParticleSystem *newParticleSystem(Texture *texture, int size);
Canvas *newCanvas(int width, int height, PixelFormat format = PIXELFORMAT_NORMAL, int msaa = 0);
Canvas *newCanvas(int width, int height, const Canvas::Settings &settings);
Shader *newShader(const Shader::ShaderSource &source);
@@ -198,7 +196,7 @@ public:
Text *newText(Font *font, const std::vector<Font::ColoredString> &text = {});
Video *newVideo(love::video::VideoStream *stream);
Video *newVideo(love::video::VideoStream *stream, float pixeldensity);
bool isGammaCorrect() const;
@@ -440,6 +438,9 @@ private:
int width;
int height;
int pixelWidth;
int pixelHeight;
bool created;
bool active;
+53 -47
View File
@@ -94,11 +94,11 @@ static bool verifyMipmapLevels(const std::vector<T> &miplevels)
return true;
}
Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags &flags)
Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Settings &settings)
: texture(0)
, mipmapSharpness(defaultMipmapSharpness)
, compressed(false)
, flags(flags)
, settings(settings)
, sRGB(false)
, usingDefaultTexture(false)
, textureMemorySize(0)
@@ -106,11 +106,14 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
if (imagedata.empty())
throw love::Exception("");
width = imagedata[0]->getWidth();
height = imagedata[0]->getHeight();
pixelWidth = imagedata[0]->getWidth();
pixelHeight = imagedata[0]->getHeight();
width = (int) (pixelWidth / settings.pixeldensity + 0.5);
height = (int) (pixelHeight / settings.pixeldensity + 0.5);
if (verifyMipmapLevels(imagedata))
this->flags.mipmaps = true;
this->settings.mipmaps = true;
for (const auto &id : imagedata)
data.push_back(id);
@@ -123,24 +126,27 @@ Image::Image(const std::vector<love::image::ImageData *> &imagedata, const Flags
++imageCount;
}
Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Flags &flags)
Image::Image(const std::vector<love::image::CompressedImageData *> &compresseddata, const Settings &settings)
: texture(0)
, mipmapSharpness(defaultMipmapSharpness)
, compressed(true)
, flags(flags)
, settings(settings)
, sRGB(false)
, usingDefaultTexture(false)
, textureMemorySize(0)
{
width = compresseddata[0]->getWidth(0);
height = compresseddata[0]->getHeight(0);
pixelWidth = compresseddata[0]->getWidth(0);
pixelHeight = compresseddata[0]->getHeight(0);
width = (int) (pixelWidth / settings.pixeldensity + 0.5);
height = (int) (pixelHeight / settings.pixeldensity + 0.5);
if (verifyMipmapLevels(compresseddata))
this->flags.mipmaps = true;
else if (flags.mipmaps && getMipmapCount(width, height) != compresseddata[0]->getMipmapCount())
this->settings.mipmaps = true;
else if (settings.mipmaps && getMipmapCount(pixelWidth, pixelHeight) != compresseddata[0]->getMipmapCount())
{
if (compresseddata[0]->getMipmapCount() == 1)
this->flags.mipmaps = false;
this->settings.mipmaps = false;
else
{
throw love::Exception("Image cannot have mipmaps: compressed image data does not have all required mipmap levels (expected %d, got %d)",
@@ -172,10 +178,9 @@ void Image::preload()
vertices[i].color = Color(255, 255, 255, 255);
// Vertices are ordered for use with triangle strips:
// 0----2
// | / |
// | / |
// 1----3
// 0---2
// | / |
// 1---3
vertices[0].x = 0.0f;
vertices[0].y = 0.0f;
vertices[1].x = 0.0f;
@@ -194,13 +199,13 @@ void Image::preload()
vertices[3].s = 1.0f;
vertices[3].t = 1.0f;
if (flags.mipmaps)
if (settings.mipmaps)
filter.mipmap = defaultMipmapFilter;
if (!isGammaCorrect())
flags.linear = false;
settings.linear = false;
if (isGammaCorrect() && !flags.linear)
if (isGammaCorrect() && !settings.linear)
sRGB = true;
else
sRGB = false;
@@ -210,7 +215,7 @@ void Image::generateMipmaps()
{
// The GL_GENERATE_MIPMAP texparameter is set in loadVolatile if we don't
// have support for glGenerateMipmap.
if (flags.mipmaps && !isCompressed() &&
if (settings.mipmaps && !isCompressed() &&
(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
{
if (gl.bugs.generateMipmapsRequiresTexture2DEnable)
@@ -239,13 +244,13 @@ void Image::loadFromCompressedData()
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
if (isGammaCorrect() && !sRGB)
flags.linear = true;
settings.linear = true;
int count = 1;
if (flags.mipmaps && cdata.size() > 1)
if (settings.mipmaps && cdata.size() > 1)
count = (int) cdata.size();
else if (flags.mipmaps)
else if (settings.mipmaps)
count = cdata[0]->getMipmapCount();
for (int i = 0; i < count; i++)
@@ -266,9 +271,9 @@ void Image::loadFromImageData()
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, sRGB);
if (isGammaCorrect() && !sRGB)
flags.linear = true;
settings.linear = true;
int mipcount = flags.mipmaps ? (int) data.size() : 1;
int mipcount = settings.mipmaps ? (int) data.size() : 1;
for (int i = 0; i < mipcount; i++)
{
@@ -310,16 +315,16 @@ bool Image::loadVolatile()
if (sRGB && (GLAD_ES_VERSION_2_0 && GLAD_EXT_sRGB && !GLAD_ES_VERSION_3_0)
&& data.size() <= 1)
{
flags.mipmaps = false;
settings.mipmaps = false;
filter.mipmap = FILTER_NONE;
}
}
// NPOT textures don't support mipmapping without full NPOT support.
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
&& (width != nextP2(width) || height != nextP2(height)))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
{
flags.mipmaps = false;
settings.mipmaps = false;
filter.mipmap = FILTER_NONE;
}
@@ -334,16 +339,16 @@ bool Image::loadVolatile()
setMipmapSharpness(mipmapSharpness);
// Use a default texture if the size is too big for the system.
if (width > gl.getMaxTextureSize() || height > gl.getMaxTextureSize())
if (pixelWidth > gl.getMaxTextureSize() || pixelHeight > gl.getMaxTextureSize())
{
loadDefaultTexture();
return true;
}
if (!flags.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
if (!settings.mipmaps && (GLAD_ES_VERSION_3_0 || GLAD_VERSION_1_0))
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, 0);
if (flags.mipmaps && !isCompressed() && data.size() <= 1 &&
if (settings.mipmaps && !isCompressed() && data.size() <= 1 &&
!(GLAD_ES_VERSION_2_0 || GLAD_VERSION_3_0 || GLAD_ARB_framebuffer_object))
{
// Auto-generate mipmaps every time the texture is modified, if
@@ -378,7 +383,7 @@ bool Image::loadVolatile()
else
textureMemorySize = data[0]->getSize();
if (flags.mipmaps)
if (settings.mipmaps)
textureMemorySize *= 1.33334;
gl.updateTextureMemorySize(prevmemsize, textureMemorySize);
@@ -406,7 +411,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
return false;
if (xoffset < 0 || yoffset < 0 || w <= 0 || h <= 0 ||
(xoffset + w) > width || (yoffset + h) > height)
(xoffset + w) > pixelWidth || (yoffset + h) > pixelHeight)
{
throw love::Exception("Invalid rectangle dimensions.");
}
@@ -424,7 +429,7 @@ bool Image::refresh(int xoffset, int yoffset, int w, int h)
bool isSRGB = sRGB;
OpenGL::TextureFormat fmt = OpenGL::convertPixelFormat(format, false, isSRGB);
int mipcount = flags.mipmaps ? (int) data.size() : 1;
int mipcount = settings.mipmaps ? (int) data.size() : 1;
// Reupload the sub-rectangle of each mip level (if we have custom mipmaps.)
for (int i = 0; i < mipcount; i++)
@@ -465,9 +470,9 @@ const std::vector<StrongRef<love::image::CompressedImageData>> &Image::getCompre
void Image::setFilter(const Texture::Filter &f)
{
if (!validateFilter(f, flags.mipmaps))
if (!validateFilter(f, settings.mipmaps))
{
if (f.mipmap != FILTER_NONE && !flags.mipmaps)
if (f.mipmap != FILTER_NONE && !settings.mipmaps)
throw love::Exception("Non-mipmapped image cannot have mipmap filtering.");
else
throw love::Exception("Invalid texture filter.");
@@ -500,7 +505,7 @@ bool Image::setWrap(const Texture::Wrap &w)
wrap = w;
if ((GLAD_ES_VERSION_2_0 && !(GLAD_ES_VERSION_3_0 || GLAD_OES_texture_npot))
&& (width != nextP2(width) || height != nextP2(height)))
&& (pixelWidth != nextP2(pixelWidth) || pixelHeight != nextP2(pixelHeight)))
{
if (wrap.s != WRAP_CLAMP || wrap.t != WRAP_CLAMP)
success = false;
@@ -543,9 +548,9 @@ float Image::getMipmapSharpness() const
return mipmapSharpness;
}
const Image::Flags &Image::getFlags() const
const Image::Settings &Image::getFlags() const
{
return flags;
return settings;
}
void Image::setDefaultMipmapSharpness(float sharpness)
@@ -583,23 +588,24 @@ bool Image::hasSRGBSupport()
return GLAD_ES_VERSION_3_0 || GLAD_EXT_sRGB || GLAD_VERSION_2_1 || GLAD_EXT_texture_sRGB;
}
bool Image::getConstant(const char *in, FlagType &out)
bool Image::getConstant(const char *in, SettingType &out)
{
return flagNames.find(in, out);
return settingTypes.find(in, out);
}
bool Image::getConstant(FlagType in, const char *&out)
bool Image::getConstant(SettingType in, const char *&out)
{
return flagNames.find(in, out);
return settingTypes.find(in, out);
}
StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM>::Entry Image::flagNameEntries[] =
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM>::Entry Image::settingTypeEntries[] =
{
{"mipmaps", FLAG_TYPE_MIPMAPS},
{"linear", FLAG_TYPE_LINEAR},
{ "mipmaps", SETTING_MIPMAPS },
{ "linear", SETTING_LINEAR },
{ "pixeldensity", SETTING_PIXELDENSITY },
};
StringMap<Image::FlagType, Image::FLAG_TYPE_MAX_ENUM> Image::flagNames(Image::flagNameEntries, sizeof(Image::flagNameEntries));
StringMap<Image::SettingType, Image::SETTING_MAX_ENUM> Image::settingTypes(Image::settingTypeEntries, sizeof(Image::settingTypeEntries));
} // opengl
} // graphics
+16 -14
View File
@@ -54,17 +54,19 @@ public:
static love::Type type;
enum FlagType
enum SettingType
{
FLAG_TYPE_MIPMAPS,
FLAG_TYPE_LINEAR,
FLAG_TYPE_MAX_ENUM
SETTING_MIPMAPS,
SETTING_LINEAR,
SETTING_PIXELDENSITY,
SETTING_MAX_ENUM
};
struct Flags
struct Settings
{
bool mipmaps = false;
bool linear = false;
float pixeldensity = 1.0f;
};
/**
@@ -75,14 +77,14 @@ public:
* array is a mipmap level. If more than the base level is present, all
* mip levels must be present.
**/
Image(const std::vector<love::image::ImageData *> &data, const Flags &flags);
Image(const std::vector<love::image::ImageData *> &data, const Settings &settings);
/**
* Creates a new Image with compressed image data.
*
* @param cdata The compressed data from which to load the image.
**/
Image(const std::vector<love::image::CompressedImageData *> &cdata, const Flags &flags);
Image(const std::vector<love::image::CompressedImageData *> &cdata, const Settings &settings);
virtual ~Image();
@@ -112,7 +114,7 @@ public:
**/
bool refresh(int xoffset, int yoffset, int w, int h);
const Flags &getFlags() const;
const Settings &getFlags() const;
static void setDefaultMipmapSharpness(float sharpness);
static float getDefaultMipmapSharpness();
@@ -122,8 +124,8 @@ public:
static bool isFormatSupported(PixelFormat pixelformat);
static bool hasSRGBSupport();
static bool getConstant(const char *in, FlagType &out);
static bool getConstant(FlagType in, const char *&out);
static bool getConstant(const char *in, SettingType &out);
static bool getConstant(SettingType in, const char *&out);
static int imageCount;
@@ -154,8 +156,8 @@ private:
// Whether this Image is using a compressed texture.
bool compressed;
// The flags used to initialize this Image.
Flags flags;
// The settings used to initialize this Image.
Settings settings;
bool sRGB;
@@ -170,8 +172,8 @@ private:
static FilterMode defaultMipmapFilter;
static float defaultMipmapSharpness;
static StringMap<FlagType, FLAG_TYPE_MAX_ENUM>::Entry flagNameEntries[];
static StringMap<FlagType, FLAG_TYPE_MAX_ENUM> flagNames;
static StringMap<SettingType, SETTING_MAX_ENUM>::Entry settingTypeEntries[];
static StringMap<SettingType, SETTING_MAX_ENUM> settingTypes;
}; // Image
+1 -11
View File
@@ -416,15 +416,10 @@ void OpenGL::useVertexAttribArrays(uint32 arraybits)
glVertexAttrib4f(ATTRIB_COLOR, 1.0f, 1.0f, 1.0f, 1.0f);
}
void OpenGL::setViewport(const Rect &v, bool canvasActive)
void OpenGL::setViewport(const Rect &v)
{
glViewport(v.x, v.y, v.w, v.h);
state.viewport = v;
// glScissor starts from the lower left, so we compensate when setting the
// scissor. When the viewport is changed, we need to manually update the
// scissor again.
setScissor(state.scissor, canvasActive);
}
Rect OpenGL::getViewport() const
@@ -446,11 +441,6 @@ void OpenGL::setScissor(const Rect &v, bool canvasActive)
state.scissor = v;
}
Rect OpenGL::getScissor() const
{
return state.scissor;
}
void OpenGL::setPointSize(float size)
{
if (GLAD_VERSION_1_0)
+1 -6
View File
@@ -244,7 +244,7 @@ public:
* Sets the OpenGL rendering viewport to the specified rectangle.
* The y-coordinate starts at the top.
**/
void setViewport(const Rect &v, bool canvasActive);
void setViewport(const Rect &v);
/**
* Gets the current OpenGL rendering viewport rectangle.
@@ -257,11 +257,6 @@ public:
**/
void setScissor(const Rect &v, bool canvasActive);
/**
* Gets the current scissor box (regardless of whether scissoring is enabled.)
**/
Rect getScissor() const;
/**
* Sets the global point size.
**/
+21 -10
View File
@@ -33,8 +33,10 @@ namespace opengl
love::Type Video::type("Video", &Drawable::type);
Video::Video(love::video::VideoStream *stream)
Video::Video(love::video::VideoStream *stream, float pixeldensity)
: stream(stream)
, width(stream->getWidth() / pixeldensity)
, height(stream->getHeight() / pixeldensity)
, filter(Texture::getDefaultFilter())
{
filter.mipmap = Texture::FILTER_NONE;
@@ -45,18 +47,17 @@ Video::Video(love::video::VideoStream *stream)
vertices[i].color = Color(255, 255, 255, 255);
// Vertices are ordered for use with triangle strips:
// 0----2
// | / |
// | / |
// 1----3
// 0---2
// | / |
// 1---3
vertices[0].x = 0.0f;
vertices[0].y = 0.0f;
vertices[1].x = 0.0f;
vertices[1].y = (float) stream->getHeight();
vertices[2].x = (float) stream->getWidth();
vertices[1].y = (float) height;
vertices[2].x = (float) width;
vertices[2].y = 0.0f;
vertices[3].x = (float) stream->getWidth();
vertices[3].y = (float) stream->getHeight();
vertices[3].x = (float) width;
vertices[3].y = (float) height;
vertices[0].s = 0.0f;
vertices[0].t = 0.0f;
@@ -185,10 +186,20 @@ void Video::setSource(love::audio::Source *source)
int Video::getWidth() const
{
return stream->getWidth();
return width;
}
int Video::getHeight() const
{
return height;
}
int Video::getPixelWidth() const
{
return stream->getWidth();
}
int Video::getPixelHeight() const
{
return stream->getHeight();
}
+7 -1
View File
@@ -42,7 +42,7 @@ public:
static love::Type type;
Video(love::video::VideoStream *stream);
Video(love::video::VideoStream *stream, float pixeldensity = 1.0f);
~Video();
// Volatile
@@ -60,6 +60,9 @@ public:
int getWidth() const;
int getHeight() const;
int getPixelWidth() const;
int getPixelHeight() const;
void setFilter(const Texture::Filter &f);
const Texture::Filter &getFilter() const;
@@ -70,6 +73,9 @@ private:
StrongRef<love::video::VideoStream> stream;
StrongRef<love::audio::Source> source;
int width;
int height;
GLuint textures[3];
Vertex vertices[4];
+2 -2
View File
@@ -90,8 +90,8 @@ int w_Canvas_newImageData(lua_State *L)
love::image::Image *image = luax_getmodule<love::image::Image>(L, love::image::Image::type);
int x = (int) luaL_optnumber(L, 2, 0);
int y = (int) luaL_optnumber(L, 3, 0);
int w = (int) luaL_optnumber(L, 4, canvas->getWidth());
int h = (int) luaL_optnumber(L, 5, canvas->getHeight());
int w = (int) luaL_optnumber(L, 4, canvas->getPixelWidth());
int h = (int) luaL_optnumber(L, 5, canvas->getPixelHeight());
love::image::ImageData *img = nullptr;
luax_catchexcept(L, [&](){ img = canvas->newImageData(image, x, y, w, h); });
@@ -188,6 +188,13 @@ int w_Font_setFallbacks(lua_State *L)
return 0;
}
int w_Font_getPixelDensity(lua_State *L)
{
Font *t = luax_checkfont(L, 1);
lua_pushnumber(L, t->getPixelDensity());
return 1;
}
static const luaL_Reg w_Font_functions[] =
{
{ "getHeight", w_Font_getHeight },
@@ -202,6 +209,7 @@ static const luaL_Reg w_Font_functions[] =
{ "getBaseline", w_Font_getBaseline },
{ "hasGlyphs", w_Font_hasGlyphs },
{ "setFallbacks", w_Font_setFallbacks },
{ "getPixelDensity", w_Font_getPixelDensity },
{ 0, 0 }
};
+88 -32
View File
@@ -32,6 +32,7 @@
#include <cassert>
#include <cstring>
#include <cstdlib>
#include <algorithm>
@@ -175,6 +176,25 @@ int w_getDimensions(lua_State *L)
return 2;
}
int w_getPixelWidth(lua_State *L)
{
lua_pushinteger(L, instance()->getPixelWidth());
return 1;
}
int w_getPixelHeight(lua_State *L)
{
lua_pushinteger(L, instance()->getPixelHeight());
return 1;
}
int w_getPixelDimensions(lua_State *L)
{
lua_pushinteger(L, instance()->getPixelWidth());
lua_pushinteger(L, instance()->getPixelHeight());
return 2;
}
int w_setCanvas(lua_State *L)
{
// Disable stencil writes.
@@ -385,13 +405,6 @@ int w_getStencilTest(lua_State *L)
return 2;
}
static const char *imageFlagName(Image::FlagType flagtype)
{
const char *name = nullptr;
Image::getConstant(flagtype, name);
return name;
}
int w_newImage(lua_State *L)
{
luax_checkgraphicscreated(L);
@@ -399,13 +412,7 @@ int w_newImage(lua_State *L)
std::vector<love::image::ImageData *> data;
std::vector<love::image::CompressedImageData *> cdata;
Image::Flags flags;
if (!lua_isnoneornil(L, 2))
{
luaL_checktype(L, 2, LUA_TTABLE);
flags.mipmaps = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_MIPMAPS), flags.mipmaps);
flags.linear = luax_boolflag(L, 2, imageFlagName(Image::FLAG_TYPE_LINEAR), flags.linear);
}
Image::Settings settings;
bool releasedata = false;
@@ -418,6 +425,20 @@ int w_newImage(lua_State *L)
love::filesystem::FileData *fdata = love::filesystem::luax_getfiledata(L, 1);
// Parse a density scale of 2.0 from "image@2x.png".
const std::string &fname = fdata->getName();
size_t namelen = fname.length();
size_t atpos = fname.rfind('@');
if (atpos != std::string::npos && atpos + 2 < namelen
&& (fname[namelen - 1] == 'x' || fname[namelen - 1] == 'X'))
{
char *end = nullptr;
float density = std::strtof(fname.c_str() + atpos + 1, &end);
if (end != nullptr && density > 0.0f)
settings.pixeldensity = density;
}
if (imagemodule->isCompressed(fdata))
{
luax_catchexcept(L,
@@ -441,12 +462,18 @@ int w_newImage(lua_State *L)
else
data.push_back(love::image::luax_checkimagedata(L, 1));
if (lua_istable(L, 2))
if (!lua_isnoneornil(L, 2))
{
lua_getfield(L, 2, imageFlagName(Image::FLAG_TYPE_MIPMAPS));
luaL_checktype(L, 2, LUA_TTABLE);
settings.mipmaps = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS), settings.mipmaps);
settings.linear = luax_boolflag(L, 2, luax_imageSettingName(Image::SETTING_LINEAR), settings.linear);
settings.pixeldensity = (float) luax_numberflag(L, 2, luax_imageSettingName(Image::SETTING_PIXELDENSITY), settings.pixeldensity);
lua_getfield(L, 2, luax_imageSettingName(Image::SETTING_MIPMAPS));
// Add all manually specified mipmap images to the array of imagedata.
// i.e. flags = {mipmaps = {mip1, mip2, ...}}.
// i.e. settings = {mipmaps = {mip1, mip2, ...}}.
if (lua_istable(L, -1))
{
for (size_t i = 1; i <= luax_objlen(L, -1); i++)
@@ -480,9 +507,9 @@ int w_newImage(lua_State *L)
luax_catchexcept(L,
[&]() {
if (!cdata.empty())
image = instance()->newImage(cdata, flags);
image = instance()->newImage(cdata, settings);
else if (!data.empty())
image = instance()->newImage(data, flags);
image = instance()->newImage(data, settings);
},
[&](bool) {
if (releasedata)
@@ -514,8 +541,20 @@ int w_newQuad(lua_State *L)
v.w = luaL_checknumber(L, 3);
v.h = luaL_checknumber(L, 4);
double sw = luaL_checknumber(L, 5);
double sh = luaL_checknumber(L, 6);
double sw = 0.0f;
double sh = 0.0f;
if (luax_istype(L, 5, Texture::type))
{
Texture *texture = luax_checktexture(L, 5);
sw = texture->getWidth();
sh = texture->getHeight();
}
else
{
sw = luaL_checknumber(L, 5);
sh = luaL_checknumber(L, 6);
}
Quad *quad = instance()->newQuad(v, sw, sh);
luax_pushtype(L, quad);
@@ -641,19 +680,31 @@ int w_newCanvas(lua_State *L)
luax_checkgraphicscreated(L);
// check if width and height are given. else default to screen dimensions.
int width = (int) luaL_optnumber(L, 1, instance()->getWidth());
int height = (int) luaL_optnumber(L, 2, instance()->getHeight());
const char *str = luaL_optstring(L, 3, "normal");
int msaa = (int) luaL_optnumber(L, 4, 0);
int width = (int) luaL_optnumber(L, 1, instance()->getWidth());
int height = (int) luaL_optnumber(L, 2, instance()->getHeight());
PixelFormat format;
if (!getConstant(str, format))
return luaL_error(L, "Invalid pixel format: %s", str);
Canvas::Settings settings;
// Default to the screen's current pixel density scale.
settings.pixeldensity = instance()->getScreenPixelDensity();
if (!lua_isnoneornil(L, 3))
{
lua_getfield(L, 3, "format");
if (!lua_isnoneornil(L, -1))
{
const char *str = luaL_checkstring(L, -1);
if (!getConstant(str, settings.format))
return luaL_error(L, "Invalid Canvas format: %s", str);
}
lua_pop(L, 1);
settings.pixeldensity = (float) luax_numberflag(L, 3, "pixeldensity", settings.pixeldensity);
settings.msaa = luax_intflag(L, 3, "msaa", settings.msaa);
}
Canvas *canvas = nullptr;
luax_catchexcept(L,
[&](){ canvas = instance()->newCanvas(width, height, format, msaa); }
);
luax_catchexcept(L, [&](){ canvas = instance()->newCanvas(width, height, settings); });
if (canvas == nullptr)
return luaL_error(L, "Canvas not created, but no error thrown. I don't even...");
@@ -1018,9 +1069,11 @@ int w_newVideo(lua_State *L)
luax_convobj(L, 1, "video", "newVideoStream");
auto stream = luax_checktype<love::video::VideoStream>(L, 1);
float pixeldensity = (float) luaL_optnumber(L, 2, 1.0);
Video *video = nullptr;
luax_catchexcept(L, [&]() { video = instance()->newVideo(stream); });
luax_catchexcept(L, [&]() { video = instance()->newVideo(stream, pixeldensity); });
luax_pushtype(L, video);
video->release();
return 1;
@@ -2107,6 +2160,9 @@ static const luaL_Reg functions[] =
{ "getWidth", w_getWidth },
{ "getHeight", w_getHeight },
{ "getDimensions", w_getDimensions },
{ "getPixelWidth", w_getPixelWidth },
{ "getPixelHeight", w_getPixelHeight },
{ "getPixelDimensions", w_getPixelDimensions },
{ "setScissor", w_setScissor },
{ "intersectScissor", w_intersectScissor },
@@ -341,16 +341,20 @@ end
love.graphics._setDefaultShaderCode(defaults, defaults_gammacorrect)
function love.graphics.newVideo(file, loadaudio)
local video = love.graphics._newVideo(file)
function love.graphics.newVideo(file, settings)
settings = settings == nil and {} or settings
if type(settings) ~= "table" then error("bad argument #2 to newVideo (expected table)", 2) end
local video = love.graphics._newVideo(file, settings.pixeldensity)
local source, success
if loadaudio ~= false and love.audio then
if settings.audio ~= false and love.audio then
success, source = pcall(love.audio.newSource, video:getStream():getFilename(), "stream")
end
if success then
video:setSource(source)
elseif loadaudio == true then
elseif settings.audio == true then
if love.audio then
error("Video had no audio track", 2)
else
+10 -7
View File
@@ -114,25 +114,28 @@ int w_Image_getData(lua_State *L)
return n;
}
static const char *imageFlagName(Image::FlagType flagtype)
const char *luax_imageSettingName(Image::SettingType settingtype)
{
const char *name = nullptr;
Image::getConstant(flagtype, name);
Image::getConstant(settingtype, name);
return name;
}
int w_Image_getFlags(lua_State *L)
{
Image *i = luax_checkimage(L, 1);
Image::Flags flags = i->getFlags();
Image::Settings settings = i->getFlags();
lua_createtable(L, 0, 2);
lua_pushboolean(L, flags.mipmaps);
lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_MIPMAPS));
lua_pushboolean(L, settings.mipmaps);
lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_MIPMAPS));
lua_pushboolean(L, flags.linear);
lua_setfield(L, -2, imageFlagName(Image::FLAG_TYPE_LINEAR));
lua_pushboolean(L, settings.linear);
lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_LINEAR));
lua_pushnumber(L, settings.pixeldensity);
lua_setfield(L, -2, luax_imageSettingName(Image::SETTING_PIXELDENSITY));
return 1;
}
+1
View File
@@ -33,6 +33,7 @@ namespace graphics
namespace opengl
{
const char *luax_imageSettingName(Image::SettingType settingtype);
Image *luax_checkimage(lua_State *L, int idx);
extern "C" int luaopen_image(lua_State *L);
@@ -90,6 +90,28 @@ int w_Video_getDimensions(lua_State *L)
return 2;
}
int w_Video_getPixelWidth(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
lua_pushnumber(L, video->getPixelWidth());
return 1;
}
int w_Video_getPixelHeight(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
lua_pushnumber(L, video->getPixelHeight());
return 1;
}
int w_Video_getPixelDimensions(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
lua_pushnumber(L, video->getPixelWidth());
lua_pushnumber(L, video->getPixelHeight());
return 2;
}
int w_Video_setFilter(lua_State *L)
{
Video *video = luax_checkvideo(L, 1);
@@ -136,6 +158,9 @@ static const luaL_Reg functions[] =
{ "getWidth", w_Video_getWidth },
{ "getHeight", w_Video_getHeight },
{ "getDimensions", w_Video_getDimensions },
{ "getPixelWidth", w_Video_getPixelWidth },
{ "getPixelHeight", w_Video_getPixelHeight },
{ "getPixelDimensions", w_Video_getPixelDimensions },
{ "setFilter", w_Video_setFilter },
{ "getFilter", w_Video_getFilter },
{ 0, 0 }
+33
View File
@@ -52,6 +52,35 @@ int w_Texture_getDimensions(lua_State *L)
return 2;
}
int w_Texture_getPixelWidth(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelWidth());
return 1;
}
int w_Texture_getPixelHeight(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelHeight());
return 1;
}
int w_Texture_getPixelDimensions(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelWidth());
lua_pushnumber(L, t->getPixelHeight());
return 2;
}
int w_Texture_getPixelDensity(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
lua_pushnumber(L, t->getPixelDensity());
return 1;
}
int w_Texture_setFilter(lua_State *L)
{
Texture *t = luax_checktexture(L, 1);
@@ -130,6 +159,10 @@ const luaL_Reg w_Texture_functions[] =
{ "getWidth", w_Texture_getWidth },
{ "getHeight", w_Texture_getHeight },
{ "getDimensions", w_Texture_getDimensions },
{ "getPixelWidth", w_Texture_getPixelWidth },
{ "getPixelHeight", w_Texture_getPixelHeight },
{ "getPixelDimensions", w_Texture_getPixelDimensions },
{ "getPixelDensity", w_Texture_getPixelDensity },
{ "setFilter", w_Texture_setFilter },
{ "getFilter", w_Texture_getFilter },
{ "setWrap", w_Texture_setWrap },
+2 -2
View File
@@ -124,8 +124,8 @@ void Keyboard::setTextInput(bool enable, double x, double y, double w, double h)
auto window = Module::getInstance<window::Window>(M_WINDOW);
if (window)
{
window->pixelToWindowCoords(&x, &y);
window->pixelToWindowCoords(&w, &h);
window->DPIToWindowCoords(&x, &y);
window->DPIToWindowCoords(&w, &h);
}
SDL_Rect rect = {(int) x, (int) y, (int) w, (int) h};
+8 -8
View File
@@ -34,19 +34,19 @@ namespace sdl
// SDL reports mouse coordinates in the window coordinate system in OS X, but
// we want them in pixel coordinates (may be different with high-DPI enabled.)
static void windowToPixelCoords(double *x, double *y)
static void windowToDPICoords(double *x, double *y)
{
auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
if (window)
window->windowToPixelCoords(x, y);
window->windowToDPICoords(x, y);
}
// And vice versa for setting mouse coordinates.
static void pixelToWindowCoords(double *x, double *y)
static void DPIToWindowCoords(double *x, double *y)
{
auto window = Module::getInstance<window::Window>(Module::M_WINDOW);
if (window)
window->pixelToWindowCoords(x, y);
window->DPIToWindowCoords(x, y);
}
const char *Mouse::getName() const
@@ -118,7 +118,7 @@ double Mouse::getX() const
SDL_GetMouseState(&x, nullptr);
double dx = (double) x;
windowToPixelCoords(&dx, nullptr);
windowToDPICoords(&dx, nullptr);
return dx;
}
@@ -129,7 +129,7 @@ double Mouse::getY() const
SDL_GetMouseState(nullptr, &y);
double dy = (double) y;
windowToPixelCoords(nullptr, &dy);
windowToDPICoords(nullptr, &dy);
return dy;
}
@@ -141,7 +141,7 @@ void Mouse::getPosition(double &x, double &y) const
x = (double) mx;
y = (double) my;
windowToPixelCoords(&x, &y);
windowToDPICoords(&x, &y);
}
void Mouse::setPosition(double x, double y)
@@ -152,7 +152,7 @@ void Mouse::setPosition(double x, double y)
if (window)
handle = (SDL_Window *) window->getHandle();
pixelToWindowCoords(&x, &y);
DPIToWindowCoords(&x, &y);
SDL_WarpMouseInWindow(handle, (int) x, (int) y);
// SDL_WarpMouse doesn't directly update SDL's internal mouse state in Linux
+9 -2
View File
@@ -164,13 +164,20 @@ public:
virtual void setMouseGrab(bool grab) = 0;
virtual bool isMouseGrabbed() const = 0;
virtual void getPixelDimensions(int &w, int &h) const = 0;
virtual int getWidth() const = 0;
virtual int getHeight() const = 0;
virtual int getPixelWidth() const = 0;
virtual int getPixelHeight() const = 0;
// Note: window-space coordinates are not necessarily the same as
// density-independent units (which toPixels and fromPixels use.)
virtual void windowToPixelCoords(double *x, double *y) const = 0;
virtual void pixelToWindowCoords(double *x, double *y) const = 0;
virtual double getPixelScale() const = 0;
virtual void windowToDPICoords(double *x, double *y) const = 0;
virtual void DPIToWindowCoords(double *x, double *y) const = 0;
virtual double getPixelDensity() const = 0;
virtual double toPixels(double x) const = 0;
virtual void toPixels(double wx, double wy, double &px, double &py) const = 0;
+73 -11
View File
@@ -521,7 +521,11 @@ bool Window::setWindow(int width, int height, WindowSettings *settings)
updateSettings(f, false);
if (graphics.get())
graphics->setMode(pixelWidth, pixelHeight);
{
double scaledw, scaledh;
fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
graphics->setMode((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
}
#ifdef LOVE_ANDROID
love::android::setImmersive(f.fullscreen);
@@ -541,7 +545,11 @@ bool Window::onSizeChanged(int width, int height)
SDL_GL_GetDrawableSize(window, &pixelWidth, &pixelHeight);
if (graphics.get())
graphics->setViewportSize(pixelWidth, pixelHeight);
{
double scaledw, scaledh;
fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
}
return true;
}
@@ -609,7 +617,11 @@ void Window::updateSettings(const WindowSettings &newsettings, bool updateGraphi
// Update the viewport size now instead of waiting for event polling.
if (updateGraphicsViewport && graphics.get())
graphics->setViewportSize(pixelWidth, pixelHeight);
{
double scaledw, scaledh;
fromPixels((double) pixelWidth, (double) pixelHeight, scaledw, scaledh);
graphics->setViewportSize((int) scaledw, (int) scaledh, pixelWidth, pixelHeight);
}
}
void Window::getWindow(int &width, int &height, WindowSettings &newsettings)
@@ -940,12 +952,27 @@ bool Window::isMouseGrabbed() const
return mouseGrabbed;
}
void Window::getPixelDimensions(int &w, int &h) const
int Window::getWidth() const
{
w = pixelWidth;
h = pixelHeight;
return windowWidth;
}
int Window::getHeight() const
{
return windowHeight;
}
int Window::getPixelWidth() const
{
return pixelWidth;
}
int Window::getPixelHeight() const
{
return pixelHeight;
}
void Window::windowToPixelCoords(double *x, double *y) const
{
if (x != nullptr)
@@ -962,7 +989,42 @@ void Window::pixelToWindowCoords(double *x, double *y) const
*y = (*y) * ((double) windowHeight / (double) pixelHeight);
}
double Window::getPixelScale() const
void Window::windowToDPICoords(double *x, double *y) const
{
double px = x != nullptr ? *x : 0.0;
double py = y != nullptr ? *y : 0.0;
windowToPixelCoords(&px, &py);
double dpix = 0.0;
double dpiy = 0.0;
fromPixels(px, py, dpix, dpiy);
if (x != nullptr)
*x = dpix;
if (y != nullptr)
*y = dpiy;
}
void Window::DPIToWindowCoords(double *x, double *y) const
{
double dpix = x != nullptr ? *x : 0.0;
double dpiy = y != nullptr ? *y : 0.0;
double px = 0.0;
double py = 0.0;
toPixels(dpix, dpiy, px, py);
pixelToWindowCoords(&px, &py);
if (x != nullptr)
*x = px;
if (y != nullptr)
*y = py;
}
double Window::getPixelDensity() const
{
#ifdef LOVE_ANDROID
return love::android::getScreenScale();
@@ -973,24 +1035,24 @@ double Window::getPixelScale() const
double Window::toPixels(double x) const
{
return x * getPixelScale();
return x * getPixelDensity();
}
void Window::toPixels(double wx, double wy, double &px, double &py) const
{
double scale = getPixelScale();
double scale = getPixelDensity();
px = wx * scale;
py = wy * scale;
}
double Window::fromPixels(double x) const
{
return x / getPixelScale();
return x / getPixelDensity();
}
void Window::fromPixels(double px, double py, double &wx, double &wy) const
{
double scale = getPixelScale();
double scale = getPixelDensity();
wx = px / scale;
wy = py / scale;
}
+9 -2
View File
@@ -90,11 +90,18 @@ public:
void setMouseGrab(bool grab);
bool isMouseGrabbed() const;
void getPixelDimensions(int &w, int &h) const;
int getWidth() const;
int getHeight() const;
int getPixelWidth() const;
int getPixelHeight() const;
void windowToPixelCoords(double *x, double *y) const;
void pixelToWindowCoords(double *x, double *y) const;
double getPixelScale() const;
void windowToDPICoords(double *x, double *y) const;
void DPIToWindowCoords(double *x, double *y) const;
double getPixelDensity() const;
double toPixels(double x) const;
void toPixels(double wx, double wy, double &px, double &py) const;
+3 -3
View File
@@ -408,9 +408,9 @@ int w_isVisible(lua_State *L)
return 1;
}
int w_getPixelScale(lua_State *L)
int w_getPixelDensity(lua_State *L)
{
lua_pushnumber(L, instance()->getPixelScale());
lua_pushnumber(L, instance()->getPixelDensity());
return 1;
}
@@ -570,7 +570,7 @@ static const luaL_Reg functions[] =
{ "hasFocus", w_hasFocus },
{ "hasMouseFocus", w_hasMouseFocus },
{ "isVisible", w_isVisible },
{ "getPixelScale", w_getPixelScale },
{ "getPixelDensity", w_getPixelDensity },
{ "toPixels", w_toPixels },
{ "fromPixels", w_fromPixels },
{ "minimize", w_minimize },
+2 -3
View File
@@ -603,8 +603,7 @@ function love.errhand(msg)
if love.audio then love.audio.stop() end
love.graphics.reset()
local font = love.graphics.setNewFont(math.floor(love.window.toPixels(14)))
local font = love.graphics.setNewFont(14)
love.graphics.setColor(1, 1, 1, 1)
@@ -630,8 +629,8 @@ function love.errhand(msg)
p = string.gsub(p, "%[string \"(.-)\"%]", "%1")
local function draw()
local pos = 70
love.graphics.clear(89/255, 157/255, 220/255)
local pos = love.window.toPixels(70)
love.graphics.printf(p, pos, pos, love.graphics.getWidth() - pos)
love.graphics.present()
end
+2 -6
View File
@@ -1103,9 +1103,7 @@ const unsigned char boot_lua[] =
0x65, 0x74, 0x28, 0x29, 0x0a,
0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x6f, 0x6e, 0x74, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65,
0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74, 0x4e, 0x65, 0x77, 0x46, 0x6f,
0x6e, 0x74, 0x28, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x6c, 0x6f, 0x76, 0x65,
0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x31,
0x34, 0x29, 0x29, 0x29, 0x0a,
0x6e, 0x74, 0x28, 0x31, 0x34, 0x29, 0x0a,
0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65, 0x74,
0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x72, 0x61, 0x63, 0x65, 0x20, 0x3d, 0x20, 0x64, 0x65, 0x62,
@@ -1140,12 +1138,10 @@ const unsigned char boot_lua[] =
0x5c, 0x22, 0x25, 0x5d, 0x22, 0x2c, 0x20, 0x22, 0x25, 0x31, 0x22, 0x29, 0x0a,
0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x64, 0x72,
0x61, 0x77, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x37, 0x30, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x63, 0x6c,
0x65, 0x61, 0x72, 0x28, 0x38, 0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x35, 0x37, 0x2f, 0x32, 0x35,
0x35, 0x2c, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x70, 0x6f, 0x73, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65,
0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x74, 0x6f, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x73, 0x28, 0x37,
0x30, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x72,
0x69, 0x6e, 0x74, 0x66, 0x28, 0x70, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20, 0x70, 0x6f, 0x73, 0x2c, 0x20,
0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57,
+29 -54
View File
@@ -633,7 +633,7 @@ function love.nogame()
end
function Toast:center()
local ww, wh = love.window.fromPixels(love.graphics.getDimensions())
local ww, wh = love.graphics.getDimensions()
self.x = math.floor(ww / 2 / 32) * 32 + 16
self.y = math.floor(wh / 2 / 32) * 32 + 16
end
@@ -727,10 +727,9 @@ function love.nogame()
function Mosaic:init()
local mosaic_image = g_images.mosaic[1]
local sw, sh = mosaic_image:getDimensions()
local ww, wh = love.window.fromPixels(love.graphics.getDimensions())
local ww, wh = love.graphics.getDimensions()
if love.window.getPixelScale() > 1 then
if love.window.getPixelDensity() > 1 then
mosaic_image = g_images.mosaic[2]
end
@@ -758,20 +757,11 @@ function love.nogame()
-- Insert only once. This way it appears half as often.
table.insert(COLORS, { 220/255, 239/255, 113/255 }) -- LIME
-- When using the higher-res mosaic sprite sheet, we want to draw its
-- sprites at the same scale as the regular-resolution one, because
-- we'll globally love.graphics.scale *everything* by the screen's
-- pixel density ratio.
-- We can avoid a lot of Quad scaling by taking advantage of the fact
-- that Quads use normalized texture coordinates internally - if we use
-- the 'source image size' and quad size of the @1x image for the Quads
-- even when rendering them using the @2x image, it will automatically
-- scale as expected.
local QUADS = {
love.graphics.newQuad(0, 0, 32, 32, sw, sh),
love.graphics.newQuad(0, 32, 32, 32, sw, sh),
love.graphics.newQuad(32, 32, 32, 32, sw, sh),
love.graphics.newQuad(32, 0, 32, 32, sw, sh),
love.graphics.newQuad(0, 0, 32, 32, mosaic_image),
love.graphics.newQuad(0, 32, 32, 32, mosaic_image),
love.graphics.newQuad(32, 32, 32, 32, mosaic_image),
love.graphics.newQuad(32, 0, 32, 32, mosaic_image),
}
local exclude_left = math.floor(ww / 2 / 32)
@@ -844,19 +834,19 @@ function love.nogame()
end
local GLYPHS = {
N = love.graphics.newQuad(0, 64, 32, 32, sw, sh),
O = love.graphics.newQuad(32, 64, 32, 32, sw, sh),
G = love.graphics.newQuad(0, 96, 32, 32, sw, sh),
A = love.graphics.newQuad(32, 96, 32, 32, sw, sh),
M = love.graphics.newQuad(64, 96, 32, 32, sw, sh),
E = love.graphics.newQuad(96, 96, 32, 32, sw, sh),
N = love.graphics.newQuad(0, 64, 32, 32, mosaic_image),
O = love.graphics.newQuad(32, 64, 32, 32, mosaic_image),
G = love.graphics.newQuad(0, 96, 32, 32, mosaic_image),
A = love.graphics.newQuad(32, 96, 32, 32, mosaic_image),
M = love.graphics.newQuad(64, 96, 32, 32, mosaic_image),
E = love.graphics.newQuad(96, 96, 32, 32, mosaic_image),
U = love.graphics.newQuad(64, 0, 32, 32, sw, sh),
P = love.graphics.newQuad(96, 0, 32, 32, sw, sh),
o = love.graphics.newQuad(64, 32, 32, 32, sw, sh),
S = love.graphics.newQuad(96, 32, 32, 32, sw, sh),
R = love.graphics.newQuad(64, 64, 32, 32, sw, sh),
T = love.graphics.newQuad(96, 64, 32, 32, sw, sh),
U = love.graphics.newQuad(64, 0, 32, 32, mosaic_image),
P = love.graphics.newQuad(96, 0, 32, 32, mosaic_image),
o = love.graphics.newQuad(64, 32, 32, 32, mosaic_image),
S = love.graphics.newQuad(96, 32, 32, 32, mosaic_image),
R = love.graphics.newQuad(64, 64, 32, 32, mosaic_image),
T = love.graphics.newQuad(96, 64, 32, 32, mosaic_image),
}
local INITIAL_TEXT_COLOR = { 15/16, 15/16, 15/16 }
@@ -932,9 +922,9 @@ function love.nogame()
function love.load()
love.graphics.setBackgroundColor(136/255, 193/255, 206/255)
local function load_image(file, name)
local function load_image(file, name, flags)
local decoded = love.math.decode("base64", file)
return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", ".")))
return love.graphics.newImage(love.filesystem.newFileData(decoded, name:gsub("_", ".")), flags)
end
g_images = {}
@@ -963,11 +953,8 @@ function love.nogame()
function love.draw()
love.graphics.setColor(1, 1, 1)
love.graphics.push()
love.graphics.scale(love.window.getPixelScale())
g_entities.mosaic:draw()
g_entities.toast:draw()
love.graphics.pop()
end
function love.resize(w, h)
@@ -988,10 +975,17 @@ function love.nogame()
end
end
function love.mousepressed(x, y, b)
function love.mousepressed(x, y, b, istouch, clicks)
local tx = x / love.graphics.getWidth()
local ty = y / love.graphics.getHeight()
g_entities.toast:look_at(tx, ty)
-- Double-tap the screen (when using a touch screen) to exit.
if istouch and clicks == 2 then
if love.window.showMessageBox("Exit No-Game Screen", "", {"OK", "Cancel"}) == 1 then
love.event.quit()
end
end
end
function love.mousemoved(x, y)
@@ -1002,25 +996,6 @@ function love.nogame()
end
end
local last_touch = {time=0, x=0, y=0}
function love.touchpressed(id, x, y, pressure)
-- Double-tap the screen (when using a touch screen) to exit.
if #love.touch.getTouches() == 1 then
local dist = math.sqrt((x-last_touch.x)^2 + (y-last_touch.y)^2)
local difftime = love.timer.getTime() - last_touch.time
if difftime < 0.3 and dist < 50 then
if love.window.showMessageBox("Exit No-Game Screen", "", {"OK", "Cancel"}) == 1 then
love.event.quit()
end
end
last_touch.time = love.timer.getTime()
last_touch.x = x
last_touch.y = y
end
end
function love.conf(t)
t.title = "L\195\150VE " .. love._version .. " (" .. love._version_codename .. ")"
t.gammacorrect = true
+57 -113
View File
@@ -2961,9 +2961,8 @@ const unsigned char nogame_lua[] =
0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x54, 0x6f, 0x61, 0x73, 0x74, 0x3a, 0x63, 0x65,
0x6e, 0x74, 0x65, 0x72, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x77, 0x2c, 0x20, 0x77, 0x68, 0x20, 0x3d, 0x20, 0x6c,
0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x69, 0x78,
0x65, 0x6c, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e,
0x67, 0x65, 0x74, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x29, 0x0a,
0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x44, 0x69,
0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x73, 0x65, 0x6c, 0x66, 0x2e, 0x78, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c,
0x6f, 0x6f, 0x72, 0x28, 0x77, 0x77, 0x20, 0x2f, 0x20, 0x32, 0x20, 0x2f, 0x20, 0x33, 0x32, 0x29, 0x20, 0x2a,
0x20, 0x33, 0x32, 0x20, 0x2b, 0x20, 0x31, 0x36, 0x0a,
@@ -3127,16 +3126,12 @@ const unsigned char nogame_lua[] =
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61,
0x67, 0x65, 0x20, 0x3d, 0x20, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61,
0x69, 0x63, 0x5b, 0x31, 0x5d, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x20, 0x3d, 0x20, 0x6d,
0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x3a, 0x67, 0x65, 0x74, 0x44, 0x69, 0x6d,
0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x77, 0x77, 0x2c, 0x20, 0x77, 0x68, 0x20, 0x3d, 0x20, 0x6c,
0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x66, 0x72, 0x6f, 0x6d, 0x50, 0x69, 0x78,
0x65, 0x6c, 0x73, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e,
0x67, 0x65, 0x74, 0x44, 0x69, 0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x29, 0x0a,
0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x44, 0x69,
0x6d, 0x65, 0x6e, 0x73, 0x69, 0x6f, 0x6e, 0x73, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67,
0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x20, 0x3e, 0x20, 0x31,
0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x65, 0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x44, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79, 0x28, 0x29, 0x20, 0x3e,
0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x3d, 0x20,
0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5b, 0x32, 0x5d, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
@@ -3184,53 +3179,23 @@ const unsigned char nogame_lua[] =
0x4f, 0x52, 0x53, 0x2c, 0x20, 0x7b, 0x20, 0x32, 0x32, 0x30, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x32, 0x33,
0x39, 0x2f, 0x32, 0x35, 0x35, 0x2c, 0x20, 0x31, 0x31, 0x33, 0x2f, 0x32, 0x35, 0x35, 0x20, 0x7d, 0x29, 0x20,
0x2d, 0x2d, 0x20, 0x4c, 0x49, 0x4d, 0x45, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68,
0x65, 0x20, 0x68, 0x69, 0x67, 0x68, 0x65, 0x72, 0x2d, 0x72, 0x65, 0x73, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69,
0x63, 0x20, 0x73, 0x70, 0x72, 0x69, 0x74, 0x65, 0x20, 0x73, 0x68, 0x65, 0x65, 0x74, 0x2c, 0x20, 0x77, 0x65,
0x20, 0x77, 0x61, 0x6e, 0x74, 0x20, 0x74, 0x6f, 0x20, 0x64, 0x72, 0x61, 0x77, 0x20, 0x69, 0x74, 0x73, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x73, 0x70, 0x72, 0x69, 0x74, 0x65, 0x73, 0x20, 0x61, 0x74, 0x20, 0x74, 0x68,
0x65, 0x20, 0x73, 0x61, 0x6d, 0x65, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x61, 0x73, 0x20, 0x74, 0x68,
0x65, 0x20, 0x72, 0x65, 0x67, 0x75, 0x6c, 0x61, 0x72, 0x2d, 0x72, 0x65, 0x73, 0x6f, 0x6c, 0x75, 0x74, 0x69,
0x6f, 0x6e, 0x20, 0x6f, 0x6e, 0x65, 0x2c, 0x20, 0x62, 0x65, 0x63, 0x61, 0x75, 0x73, 0x65, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x77, 0x65, 0x27, 0x6c, 0x6c, 0x20, 0x67, 0x6c, 0x6f, 0x62, 0x61, 0x6c, 0x6c,
0x79, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x20, 0x2a, 0x65, 0x76, 0x65, 0x72, 0x79, 0x74, 0x68, 0x69, 0x6e, 0x67, 0x2a, 0x20, 0x62,
0x79, 0x20, 0x74, 0x68, 0x65, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x27, 0x73, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x70, 0x69, 0x78, 0x65, 0x6c, 0x20, 0x64, 0x65, 0x6e, 0x73, 0x69, 0x74, 0x79,
0x20, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x2e, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x57, 0x65, 0x20, 0x63, 0x61, 0x6e, 0x20, 0x61, 0x76, 0x6f, 0x69, 0x64, 0x20,
0x61, 0x20, 0x6c, 0x6f, 0x74, 0x20, 0x6f, 0x66, 0x20, 0x51, 0x75, 0x61, 0x64, 0x20, 0x73, 0x63, 0x61, 0x6c,
0x69, 0x6e, 0x67, 0x20, 0x62, 0x79, 0x20, 0x74, 0x61, 0x6b, 0x69, 0x6e, 0x67, 0x20, 0x61, 0x64, 0x76, 0x61,
0x6e, 0x74, 0x61, 0x67, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x66, 0x61, 0x63, 0x74, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x51, 0x75, 0x61, 0x64, 0x73, 0x20, 0x75, 0x73,
0x65, 0x20, 0x6e, 0x6f, 0x72, 0x6d, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x64, 0x20, 0x74, 0x65, 0x78, 0x74, 0x75,
0x72, 0x65, 0x20, 0x63, 0x6f, 0x6f, 0x72, 0x64, 0x69, 0x6e, 0x61, 0x74, 0x65, 0x73, 0x20, 0x69, 0x6e, 0x74,
0x65, 0x72, 0x6e, 0x61, 0x6c, 0x6c, 0x79, 0x20, 0x2d, 0x20, 0x69, 0x66, 0x20, 0x77, 0x65, 0x20, 0x75, 0x73,
0x65, 0x20, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x74, 0x68, 0x65, 0x20, 0x27, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x20, 0x69,
0x6d, 0x61, 0x67, 0x65, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x27, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x71, 0x75, 0x61,
0x64, 0x20, 0x73, 0x69, 0x7a, 0x65, 0x20, 0x6f, 0x66, 0x20, 0x74, 0x68, 0x65, 0x20, 0x40, 0x31, 0x78, 0x20,
0x69, 0x6d, 0x61, 0x67, 0x65, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x51, 0x75, 0x61, 0x64,
0x73, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x65, 0x76, 0x65, 0x6e, 0x20, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x72, 0x65, 0x6e,
0x64, 0x65, 0x72, 0x69, 0x6e, 0x67, 0x20, 0x74, 0x68, 0x65, 0x6d, 0x20, 0x75, 0x73, 0x69, 0x6e, 0x67, 0x20,
0x74, 0x68, 0x65, 0x20, 0x40, 0x32, 0x78, 0x20, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x2c, 0x20, 0x69, 0x74, 0x20,
0x77, 0x69, 0x6c, 0x6c, 0x20, 0x61, 0x75, 0x74, 0x6f, 0x6d, 0x61, 0x74, 0x69, 0x63, 0x61, 0x6c, 0x6c, 0x79, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x73, 0x63, 0x61, 0x6c, 0x65, 0x20, 0x61, 0x73, 0x20, 0x65, 0x78, 0x70, 0x65,
0x63, 0x74, 0x65, 0x64, 0x2e, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x51, 0x55, 0x41, 0x44, 0x53, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e,
0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29,
0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e,
0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29,
0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e,
0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29,
0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x6e,
0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x30, 0x2c, 0x20, 0x20, 0x33, 0x32, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x29,
0x2c, 0x0a,
0x09, 0x09, 0x7d, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x65, 0x78, 0x63, 0x6c, 0x75, 0x64, 0x65, 0x5f, 0x6c, 0x65,
0x66, 0x74, 0x20, 0x3d, 0x20, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x66, 0x6c, 0x6f, 0x6f, 0x72, 0x28, 0x77, 0x77,
@@ -3383,40 +3348,52 @@ const unsigned char nogame_lua[] =
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x47, 0x4c, 0x59, 0x50, 0x48, 0x53, 0x20, 0x3d, 0x20, 0x7b, 0x0a,
0x09, 0x09, 0x09, 0x4e, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x36, 0x34, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x4f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x36, 0x34, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x47, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x30, 0x2c, 0x20, 0x20, 0x39, 0x36, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x41, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x33, 0x32, 0x2c, 0x20, 0x39, 0x36, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x4d, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x39, 0x36, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x45, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x39, 0x36, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x55, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x30, 0x2c, 0x20,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x50, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x30, 0x2c, 0x20,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x6f, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x33, 0x32, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x53, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x33, 0x32, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x52, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x36, 0x34, 0x2c, 0x20, 0x36, 0x34, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x09, 0x54, 0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69,
0x63, 0x73, 0x2e, 0x6e, 0x65, 0x77, 0x51, 0x75, 0x61, 0x64, 0x28, 0x39, 0x36, 0x2c, 0x20, 0x36, 0x34, 0x2c,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x73, 0x77, 0x2c, 0x20, 0x73, 0x68, 0x29, 0x2c, 0x0a,
0x20, 0x33, 0x32, 0x2c, 0x20, 0x33, 0x32, 0x2c, 0x20, 0x6d, 0x6f, 0x73, 0x61, 0x69, 0x63, 0x5f, 0x69, 0x6d,
0x61, 0x67, 0x65, 0x29, 0x2c, 0x0a,
0x09, 0x09, 0x7d, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x49, 0x4e, 0x49, 0x54, 0x49, 0x41, 0x4c, 0x5f, 0x54, 0x45,
0x58, 0x54, 0x5f, 0x43, 0x4f, 0x4c, 0x4f, 0x52, 0x20, 0x3d, 0x20, 0x7b, 0x20, 0x31, 0x35, 0x2f, 0x31, 0x36,
@@ -3561,7 +3538,7 @@ const unsigned char nogame_lua[] =
0x30, 0x36, 0x2f, 0x32, 0x35, 0x35, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c,
0x6f, 0x61, 0x64, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x28, 0x66, 0x69, 0x6c, 0x65, 0x2c, 0x20, 0x6e, 0x61,
0x6d, 0x65, 0x29, 0x0a,
0x6d, 0x65, 0x2c, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x29, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x20, 0x3d,
0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x61, 0x74, 0x68, 0x2e, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x28,
0x22, 0x62, 0x61, 0x73, 0x65, 0x36, 0x34, 0x22, 0x2c, 0x20, 0x66, 0x69, 0x6c, 0x65, 0x29, 0x0a,
@@ -3570,7 +3547,7 @@ const unsigned char nogame_lua[] =
0x65, 0x2e, 0x66, 0x69, 0x6c, 0x65, 0x73, 0x79, 0x73, 0x74, 0x65, 0x6d, 0x2e, 0x6e, 0x65, 0x77, 0x46, 0x69,
0x6c, 0x65, 0x44, 0x61, 0x74, 0x61, 0x28, 0x64, 0x65, 0x63, 0x6f, 0x64, 0x65, 0x64, 0x2c, 0x20, 0x6e, 0x61,
0x6d, 0x65, 0x3a, 0x67, 0x73, 0x75, 0x62, 0x28, 0x22, 0x5f, 0x22, 0x2c, 0x20, 0x22, 0x2e, 0x22, 0x29, 0x29,
0x29, 0x0a,
0x2c, 0x20, 0x66, 0x6c, 0x61, 0x67, 0x73, 0x29, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x20, 0x3d, 0x20, 0x7b, 0x7d, 0x0a,
0x09, 0x09, 0x67, 0x5f, 0x69, 0x6d, 0x61, 0x67, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74, 0x20, 0x3d,
@@ -3628,17 +3605,10 @@ const unsigned char nogame_lua[] =
0x77, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x65,
0x74, 0x43, 0x6f, 0x6c, 0x6f, 0x72, 0x28, 0x31, 0x2c, 0x20, 0x31, 0x2c, 0x20, 0x31, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x75,
0x73, 0x68, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x73, 0x63,
0x61, 0x6c, 0x65, 0x28, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e, 0x67, 0x65,
0x74, 0x50, 0x69, 0x78, 0x65, 0x6c, 0x53, 0x63, 0x61, 0x6c, 0x65, 0x28, 0x29, 0x29, 0x0a,
0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x6d, 0x6f, 0x73, 0x61, 0x69,
0x63, 0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74,
0x3a, 0x64, 0x72, 0x61, 0x77, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x70, 0x6f,
0x70, 0x28, 0x29, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x72, 0x65, 0x73,
0x69, 0x7a, 0x65, 0x28, 0x77, 0x2c, 0x20, 0x68, 0x29, 0x0a,
@@ -3668,7 +3638,8 @@ const unsigned char nogame_lua[] =
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75,
0x73, 0x65, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x62, 0x29, 0x0a,
0x73, 0x65, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x2c, 0x20, 0x62, 0x2c,
0x20, 0x69, 0x73, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2c, 0x20, 0x63, 0x6c, 0x69, 0x63, 0x6b, 0x73, 0x29, 0x0a,
0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x74, 0x78, 0x20, 0x3d, 0x20, 0x78, 0x20, 0x2f, 0x20, 0x6c,
0x6f, 0x76, 0x65, 0x2e, 0x67, 0x72, 0x61, 0x70, 0x68, 0x69, 0x63, 0x73, 0x2e, 0x67, 0x65, 0x74, 0x57, 0x69,
0x64, 0x74, 0x68, 0x28, 0x29, 0x0a,
@@ -3677,6 +3648,21 @@ const unsigned char nogame_lua[] =
0x69, 0x67, 0x68, 0x74, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x67, 0x5f, 0x65, 0x6e, 0x74, 0x69, 0x74, 0x69, 0x65, 0x73, 0x2e, 0x74, 0x6f, 0x61, 0x73, 0x74,
0x3a, 0x6c, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x74, 0x28, 0x74, 0x78, 0x2c, 0x20, 0x74, 0x79, 0x29, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x2d, 0x74, 0x61, 0x70, 0x20, 0x74, 0x68,
0x65, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x28, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69,
0x6e, 0x67, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x29,
0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x69, 0x74, 0x2e, 0x0a,
0x09, 0x09, 0x69, 0x66, 0x20, 0x69, 0x73, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x63,
0x6c, 0x69, 0x63, 0x6b, 0x73, 0x20, 0x3d, 0x3d, 0x20, 0x32, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77, 0x2e,
0x73, 0x68, 0x6f, 0x77, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x28, 0x22, 0x45, 0x78,
0x69, 0x74, 0x20, 0x4e, 0x6f, 0x2d, 0x47, 0x61, 0x6d, 0x65, 0x20, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x22,
0x2c, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x7b, 0x22, 0x4f, 0x4b, 0x22, 0x2c, 0x20, 0x22, 0x43, 0x61, 0x6e, 0x63,
0x65, 0x6c, 0x22, 0x7d, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x71, 0x75, 0x69,
0x74, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x6d, 0x6f, 0x75,
0x73, 0x65, 0x6d, 0x6f, 0x76, 0x65, 0x64, 0x28, 0x78, 0x2c, 0x20, 0x79, 0x29, 0x0a,
@@ -3692,48 +3678,6 @@ const unsigned char nogame_lua[] =
0x74, 0x3a, 0x6c, 0x6f, 0x6f, 0x6b, 0x5f, 0x61, 0x74, 0x28, 0x74, 0x78, 0x2c, 0x20, 0x74, 0x79, 0x29, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20,
0x3d, 0x20, 0x7b, 0x74, 0x69, 0x6d, 0x65, 0x3d, 0x30, 0x2c, 0x20, 0x78, 0x3d, 0x30, 0x2c, 0x20, 0x79, 0x3d,
0x30, 0x7d, 0x0a,
0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x6f, 0x75,
0x63, 0x68, 0x70, 0x72, 0x65, 0x73, 0x73, 0x65, 0x64, 0x28, 0x69, 0x64, 0x2c, 0x20, 0x78, 0x2c, 0x20, 0x79,
0x2c, 0x20, 0x70, 0x72, 0x65, 0x73, 0x73, 0x75, 0x72, 0x65, 0x29, 0x0a,
0x09, 0x09, 0x2d, 0x2d, 0x20, 0x44, 0x6f, 0x75, 0x62, 0x6c, 0x65, 0x2d, 0x74, 0x61, 0x70, 0x20, 0x74, 0x68,
0x65, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x20, 0x28, 0x77, 0x68, 0x65, 0x6e, 0x20, 0x75, 0x73, 0x69,
0x6e, 0x67, 0x20, 0x61, 0x20, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x20, 0x73, 0x63, 0x72, 0x65, 0x65, 0x6e, 0x29,
0x20, 0x74, 0x6f, 0x20, 0x65, 0x78, 0x69, 0x74, 0x2e, 0x0a,
0x09, 0x09, 0x69, 0x66, 0x20, 0x23, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x67,
0x65, 0x74, 0x54, 0x6f, 0x75, 0x63, 0x68, 0x65, 0x73, 0x28, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74,
0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x69, 0x73, 0x74, 0x20, 0x3d, 0x20, 0x6d, 0x61,
0x74, 0x68, 0x2e, 0x73, 0x71, 0x72, 0x74, 0x28, 0x28, 0x78, 0x2d, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f,
0x75, 0x63, 0x68, 0x2e, 0x78, 0x29, 0x5e, 0x32, 0x20, 0x2b, 0x20, 0x28, 0x79, 0x2d, 0x6c, 0x61, 0x73, 0x74,
0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x79, 0x29, 0x5e, 0x32, 0x29, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x6f, 0x63, 0x61, 0x6c, 0x20, 0x64, 0x69, 0x66, 0x66, 0x74, 0x69, 0x6d, 0x65, 0x20,
0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x74, 0x54, 0x69,
0x6d, 0x65, 0x28, 0x29, 0x20, 0x2d, 0x20, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e,
0x74, 0x69, 0x6d, 0x65, 0x0a,
0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x64, 0x69, 0x66, 0x66, 0x74, 0x69, 0x6d, 0x65, 0x20, 0x3c, 0x20, 0x30,
0x2e, 0x33, 0x20, 0x61, 0x6e, 0x64, 0x20, 0x64, 0x69, 0x73, 0x74, 0x20, 0x3c, 0x20, 0x35, 0x30, 0x20, 0x74,
0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x09, 0x69, 0x66, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x77, 0x69, 0x6e, 0x64, 0x6f, 0x77,
0x2e, 0x73, 0x68, 0x6f, 0x77, 0x4d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x42, 0x6f, 0x78, 0x28, 0x22, 0x45,
0x78, 0x69, 0x74, 0x20, 0x4e, 0x6f, 0x2d, 0x47, 0x61, 0x6d, 0x65, 0x20, 0x53, 0x63, 0x72, 0x65, 0x65, 0x6e,
0x22, 0x2c, 0x20, 0x22, 0x22, 0x2c, 0x20, 0x7b, 0x22, 0x4f, 0x4b, 0x22, 0x2c, 0x20, 0x22, 0x43, 0x61, 0x6e,
0x63, 0x65, 0x6c, 0x22, 0x7d, 0x29, 0x20, 0x3d, 0x3d, 0x20, 0x31, 0x20, 0x74, 0x68, 0x65, 0x6e, 0x0a,
0x09, 0x09, 0x09, 0x09, 0x09, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x65, 0x76, 0x65, 0x6e, 0x74, 0x2e, 0x71, 0x75,
0x69, 0x74, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x74, 0x69, 0x6d, 0x65,
0x20, 0x3d, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x2e, 0x67, 0x65, 0x74, 0x54,
0x69, 0x6d, 0x65, 0x28, 0x29, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x78, 0x20, 0x3d, 0x20,
0x78, 0x0a,
0x09, 0x09, 0x09, 0x6c, 0x61, 0x73, 0x74, 0x5f, 0x74, 0x6f, 0x75, 0x63, 0x68, 0x2e, 0x79, 0x20, 0x3d, 0x20,
0x79, 0x0a,
0x09, 0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x65, 0x6e, 0x64, 0x0a,
0x09, 0x66, 0x75, 0x6e, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x20, 0x6c, 0x6f, 0x76, 0x65, 0x2e, 0x63, 0x6f, 0x6e,
0x66, 0x28, 0x74, 0x29, 0x0a,
0x09, 0x09, 0x74, 0x2e, 0x74, 0x69, 0x74, 0x6c, 0x65, 0x20, 0x3d, 0x20, 0x22, 0x4c, 0x5c, 0x31, 0x39, 0x35,