-
10STYLEGUIDE.md
-
6conf.lua
-
4curves.lua
-
8data/towers.lua
-
12lib/color.lua
-
100lib/extra.lua
-
23lib/geometry.lua
-
223lib/gui.lua
-
33lib/memory.lua
-
76lib/random.lua
-
25lib/sound.lua
-
105lib/texture.lua
-
275main.lua
-
BINres/cells.jpg
-
0res/img/abouthex.png
-
0res/img/bagel.jpg
-
0res/img/button1.png
-
0res/img/curtain1.png
-
0res/img/gear.png
-
0res/img/gears.png
-
0res/img/gem1.png
-
0res/img/loadgamehex.png
-
0res/img/logo.png
-
0res/img/mainmenuhex.png
-
0res/img/mapeditorhex.png
-
0res/img/mob_beeper.png
-
0res/img/mob_spooder.png
-
0res/img/mob_velkooz.png
-
0res/img/mob_velkooz0.png
-
0res/img/mob_velkooz1.png
-
0res/img/mob_velkooz2.png
-
0res/img/mob_velkooz3.png
-
0res/img/newgamehex.png
-
0res/img/quithex.png
-
0res/img/savegamehex.png
-
0res/img/select_box.png
-
0res/img/settingshex.png
-
0res/img/shaded_hex.png
-
0res/img/slider.png
-
0res/img/sound-off.png
-
0res/img/sound-on.png
-
0res/img/tower_gattler.png
-
0res/img/tower_gattler_icon.png
-
0res/img/tower_howitzer.png
-
0res/img/tower_howitzer_icon.png
-
0res/img/tower_lighthouse.png
-
0res/img/tower_lighthouse_icon.png
-
0res/img/tower_moat.png
-
0res/img/tower_moat_icon.png
-
0res/img/tower_radar.png
-
0res/img/tower_radar_icon.png
-
0res/img/tower_redeye.png
-
0res/img/tower_redeye_icon.png
-
0res/img/tower_wall.png
-
0res/img/tower_wall_icon.png
-
0res/img/unpausehex.png
-
BINres/img/white-texture.png
-
0res/img/wider_button1.png
-
0res/ogg/main_theme.ogg
-
4src/entity.lua
-
1src/extra.lua
-
32src/game.lua
-
116src/grid.lua
-
65src/hexyz.lua
-
16src/map-editor.lua
-
3src/memory.lua
-
87src/tower.lua
-
84texture.lua
@ -1,10 +0,0 @@ |
|||
|
|||
### |
|||
this is a project written in lua, using the amulet game engine. |
|||
if you don't know lua, or know lua but don't know amulet, read or skim the documentation: |
|||
https://amulet.xyz/doc |
|||
|
|||
amulet does extend lua syntax in some small ways. |
|||
|
|||
@TODO |
|||
|
@ -1,4 +0,0 @@ |
|||
|
|||
-- nice curve, f(0) = ~1.1, f(100) = ~100, exponential slope roughly inbetween |
|||
-- math.exp((x + 1)/22) / 100 |
|||
-- |
@ -0,0 +1,100 @@ |
|||
|
|||
-- utility functions that don't below elsewhere go here, |
|||
-- especially if they would be at home on the global 'math' or 'table' variables, or are otherwise extensions of standard lua features |
|||
-- try to avoid *too* much amulet specific stuff, but vector types are probably ok. |
|||
|
|||
function fprofile(f, ...) |
|||
local t1 = am.current_time() |
|||
local result = { f(...) } |
|||
local time = am.current_time() - t1 |
|||
--log("%f", time) |
|||
return time, unpack(result) |
|||
end |
|||
|
|||
function math.wrapf(float, range) |
|||
return float - range * math.floor(float / range) |
|||
end |
|||
|
|||
function math.lerp(v1, v2, t) |
|||
return v1 * t + v2 * (1 - t) |
|||
end |
|||
|
|||
-- don't use this with sparse arrays |
|||
function table.rchoice(t) |
|||
return t[math.floor(math.random() * #t) + 1] |
|||
end |
|||
|
|||
function table.count(t) |
|||
local count = 0 |
|||
for i,v in pairs(t) do |
|||
if v ~= nil then |
|||
count = count + 1 |
|||
end |
|||
end |
|||
return count |
|||
end |
|||
|
|||
function table.highest_index(t) |
|||
local highest = nil |
|||
for i,v in pairs(t) do |
|||
if i and not highest then |
|||
highest = i |
|||
end |
|||
|
|||
if i > highest then |
|||
highest = i |
|||
end |
|||
end |
|||
return highest |
|||
end |
|||
|
|||
function table.find(t, predicate) |
|||
for i,v in pairs(t) do |
|||
if predicate(v) then |
|||
return i,v |
|||
end |
|||
end |
|||
return nil |
|||
end |
|||
|
|||
-- don't use with sparse arrays or hash tables. |
|||
-- only arrays. |
|||
-- mutates the array in place. |
|||
function table.reverse(t) |
|||
local n = #t |
|||
for i,v in pairs(t) do |
|||
t[i], t[n] = t[n], t[i] |
|||
n = n - 1 |
|||
end |
|||
end |
|||
|
|||
function table.quicksort(t, low_index, high_index, comparator) |
|||
local function partition(t, low_index, high_index) |
|||
local i = low_index - 1 |
|||
local pivot = t[high_index] |
|||
|
|||
for j = low_index, high_index - 1 do |
|||
if comparator(t[j], t[pivot]) <= 0 then |
|||
i = i + 1 |
|||
t[i], t[j] = t[j], t[i] |
|||
end |
|||
end |
|||
|
|||
t[i + 1], t[high_index] = t[high_index], t[i + 1] |
|||
return i + 1 |
|||
end |
|||
|
|||
if #t == 1 then |
|||
return t |
|||
end |
|||
|
|||
if comparator(t[low_index], t[high_index]) < 0 then |
|||
local partition_index = partition(t, low_index, high_index) |
|||
|
|||
quicksort(t, low_index, partition_index - 1, comparator) |
|||
quicksort(t, partition_index + 1, high_index, comparator) |
|||
end |
|||
|
|||
return t |
|||
end |
|||
|
@ -0,0 +1,23 @@ |
|||
|
|||
function circles_intersect(center1, center2, radius1, radius2) |
|||
local c1, c2, r1, r2 = center1, center2, radius1, radius2 |
|||
local d = math.distance(center1, center2) |
|||
local radii_sum = r1 + r2 |
|||
-- touching |
|||
if d == radii_sum then return 1 |
|||
|
|||
-- not touching or intersecting |
|||
elseif d > radii_sum then return false |
|||
|
|||
-- intersecting |
|||
else return 2 |
|||
end |
|||
end |
|||
|
|||
function point_in_rect(point, rect) |
|||
return point.x > rect.x1 |
|||
and point.x < rect.x2 |
|||
and point.y > rect.y1 |
|||
and point.y < rect.y2 |
|||
end |
|||
|
@ -0,0 +1,223 @@ |
|||
|
|||
-- text popup in the middle of the screen that dissapates |
|||
function gui_alert(message, color, decay_time) |
|||
win.scene:append( |
|||
am.scale(3) ^ am.text(message, color or COLORS.WHITE) |
|||
:action(coroutine.create(function(self) |
|||
am.wait(am.tween(self, decay_time or 1, { color = vec4(0) }, am.ease_in_out)) |
|||
win.scene:remove(self) |
|||
end)) |
|||
) |
|||
end |
|||
|
|||
local function gui_make_backing_rect( |
|||
position, |
|||
content_width, |
|||
content_height, |
|||
padding |
|||
) |
|||
local half_width = content_width/2 |
|||
local half_height = content_height/2 |
|||
|
|||
local x1 = position[1] - half_width - padding |
|||
local y1 = position[2] - half_height - padding |
|||
local x2 = position[1] + half_width + padding |
|||
local y2 = position[2] + half_height + padding |
|||
|
|||
return x1, y1, x2, y2 |
|||
end |
|||
|
|||
-- args { |
|||
-- position vec2 |
|||
-- onclick function |
|||
-- padding number |
|||
-- |
|||
-- min_width number |
|||
-- min_height number |
|||
-- |
|||
-- label string |
|||
-- font { |
|||
-- color vec4 |
|||
-- halign "center" | "left" | "right" |
|||
-- valign "center" | "top" | "bottom" |
|||
-- } |
|||
-- } |
|||
function gui_make_button(args) |
|||
local args = args or {} |
|||
|
|||
local position = args.position or vec2(0) |
|||
local onclick = args.onclick |
|||
local padding = args.padding or 6 |
|||
|
|||
local min_width = args.min_width or 0 |
|||
local min_height = args.min_height or 0 |
|||
|
|||
local label = args.label or "" |
|||
local font = args.font or { |
|||
color = vec4(1), |
|||
halign = "center", |
|||
valign = "center" |
|||
} |
|||
|
|||
local scene = am.group() |
|||
|
|||
local text = am.text(args.label or "", font.color, font.halign, font.valign) |
|||
scene:append(am.translate(args.position) ^ text) |
|||
|
|||
local content_width = math.max(min_width, text.width) |
|||
local content_height = math.max(min_height, text.height) |
|||
|
|||
local x1, y1, x2, y2 = gui_make_backing_rect(position, content_width, content_height, padding) |
|||
|
|||
local back_rect = am.rect(x1 - padding/2, y1, x2, y2 + padding/2, vec4(0, 0, 0, 1)) |
|||
local front_rect = am.rect(x1, y1, x2, y2, vec4(0.4)) |
|||
scene:prepend(front_rect) |
|||
scene:prepend(back_rect) |
|||
|
|||
scene:action(function(self) |
|||
if point_in_rect(win:mouse_position(), back_rect) then |
|||
if win:mouse_pressed"left" then |
|||
front_rect.color = vec4(0.4) |
|||
|
|||
if onclick then |
|||
onclick() |
|||
end |
|||
else |
|||
front_rect.color = vec4(0, 0, 0, 1) |
|||
end |
|||
else |
|||
front_rect.color = vec4(0.4) |
|||
end |
|||
end) |
|||
|
|||
return scene |
|||
end |
|||
|
|||
function gui_textfield( |
|||
position, |
|||
dimensions, |
|||
max, |
|||
disallowed_chars |
|||
) |
|||
local width, height = dimensions.x, dimensions.y |
|||
local disallowed_chars = disallowed_chars or {} |
|||
local max = max or 10 |
|||
local padding = padding or 6 |
|||
local half_width = width/2 |
|||
local half_height = height/2 |
|||
|
|||
local x1 = position[1] - half_width - padding |
|||
local y1 = position[2] - half_height - padding |
|||
local x2 = position[1] + half_width + padding |
|||
local y2 = position[2] + half_height + padding |
|||
|
|||
local back_rect = am.rect(x1 - padding/2, y1, x2, y2 + padding/2, vec4(0, 0, 0, 1)) |
|||
local front_rect = am.rect(x1, y1, x2, y2, vec4(0.4)) |
|||
|
|||
local group = am.group{ |
|||
back_rect, |
|||
front_rect, |
|||
am.translate(-width/2 + padding, 0) ^ am.scale(2) ^ am.text("", vec4(0, 0, 0, 1), "left"), |
|||
am.translate(-width/2 + padding, -8) ^ am.line(vec2(0, 0), vec2(16, 0), 2, vec4(0, 0, 0, 1)) |
|||
} |
|||
|
|||
group:action(function(self) |
|||
local keys = win:keys_pressed() |
|||
if #keys == 0 then return end |
|||
|
|||
local chars = {} |
|||
local shift = win:key_down("lshift") or win:key_down("rshift") |
|||
for i,k in pairs(keys) do |
|||
if k:len() == 1 then -- @HACK alphabetical or digit characters |
|||
if string.match(k, "%a") then |
|||
if shift then |
|||
table.insert(chars, k:upper()) |
|||
else |
|||
table.insert(chars, k) |
|||
end |
|||
elseif string.match(k, "%d") then |
|||
if shift then |
|||
if k == "1" then table.insert(chars, "!") |
|||
elseif k == "2" then table.insert(chars, "@") |
|||
elseif k == "3" then table.insert(chars, "#") |
|||
elseif k == "4" then table.insert(chars, "$") |
|||
elseif k == "5" then table.insert(chars, "%") |
|||
elseif k == "6" then table.insert(chars, "^") |
|||
elseif k == "7" then table.insert(chars, "&") |
|||
elseif k == "8" then table.insert(chars, "*") |
|||
elseif k == "9" then table.insert(chars, "(") |
|||
elseif k == "0" then table.insert(chars, ")") |
|||
end |
|||
else |
|||
table.insert(chars, k) |
|||
end |
|||
end |
|||
-- begin non-alphabetical/digit |
|||
elseif k == "minus" then |
|||
if shift then table.insert(chars, "_") |
|||
else table.insert(chars, "-") end |
|||
elseif k == "equals" then |
|||
if shift then table.insert(chars, "=") |
|||
else table.insert(chars, "+") end |
|||
elseif k == "leftbracket" then |
|||
if shift then table.insert(chars, "{") |
|||
else table.insert(chars, "[") end |
|||
elseif k == "rightbracket" then |
|||
if shift then table.insert(chars, "}") |
|||
else table.insert(chars, "]") end |
|||
elseif k == "backslash" then |
|||
if shift then table.insert(chars, "|") |
|||
else table.insert(chars, "\\") end |
|||
elseif k == "semicolon" then |
|||
if shift then table.insert(chars, ":") |
|||
else table.insert(chars, ";") end |
|||
elseif k == "quote" then |
|||
if shift then table.insert(chars, "\"") |
|||
else table.insert(chars, "'") end |
|||
elseif k == "backquote" then |
|||
if shift then table.insert(chars, "~") |
|||
else table.insert(chars, "`") end |
|||
elseif k == "comma" then |
|||
if shift then table.insert(chars, "<") |
|||
else table.insert(chars, ",") end |
|||
elseif k == "period" then |
|||
if shift then table.insert(chars, ">") |
|||
else table.insert(chars, ".") end |
|||
elseif k == "slash" then |
|||
if shift then table.insert(chars, "?") |
|||
else table.insert(chars, "/") end |
|||
|
|||
-- control characters |
|||
elseif k == "backspace" then |
|||
-- @NOTE this doesn't preserve the order of chars in the array so if |
|||
-- someone presses a the key "a" then the backspace key in the same frame, in that order |
|||
-- the backspace occurs first |
|||
self"text".text = self"text".text:sub(1, self"text".text:len() - 1) |
|||
|
|||
elseif k == "tab" then |
|||
-- @TODO |
|||
|
|||
elseif k == "space" then |
|||
table.insert(chars, " ") |
|||
|
|||
elseif k == "capslock" then |
|||
-- @TODO |
|||
end |
|||
end |
|||
|
|||
for _,c in pairs(chars) do |
|||
if not disallowed_chars[c] then |
|||
if self"text".text:len() <= max then |
|||
self"text".text = self"text".text .. c |
|||
end |
|||
end |
|||
end |
|||
end) |
|||
|
|||
return group |
|||
end |
|||
|
|||
function gui_open_modal() |
|||
|
|||
end |
|||
|
@ -0,0 +1,33 @@ |
|||
|
|||
-- the garbage collector decides when to run its cycles on its own, and this can cause frame spikes in your game. |
|||
-- lua provides some amount of control over its garbage collector. |
|||
-- |
|||
-- by storing the average time it takes for a full gc cycle to run, we can check at the end of a frame if we have enough time |
|||
-- to run it for 'free' |
|||
-- |
|||
-- if you wish, you can call 'collectgarbage("stop")', and then: |
|||
-- at the start of each game frame, call and cache the results of 'am.current_time()' - am.frame_time doesn't seem to work as well |
|||
-- at the end of each game frame, call 'check_if_can_collect_garbage_for_free()' with the cached frame time and a desired minimum fps |
|||
-- |
|||
local garbage_collector_cycle_timing_history = {} |
|||
local garbage_collector_average_cycle_time = 0 |
|||
function run_garbage_collector_cycle() |
|||
local time, result = fprofile(collectgarbage, "collect") |
|||
|
|||
table.insert(garbage_collector_cycle_timing_history, time) |
|||
-- re-calc average gc timing |
|||
local total = 0 |
|||
for _,v in pairs(garbage_collector_cycle_timing_history) do |
|||
total = total + v |
|||
end |
|||
garbage_collector_average_cycle_time = total / #garbage_collector_cycle_timing_history |
|||
end |
|||
|
|||
function check_if_can_collect_garbage_for_free(frame_start_time, min_fps) |
|||
-- often this will be polled at the end of a frame to see if we're running fast or slow, |
|||
-- and if we have some time to kill before the start of the next frame, we could maybe run gc. |
|||
if (am.current_time() - frame_start_time) < (1 / (min_fps or 60) + garbage_collector_average_cycle_time) then |
|||
run_garbage_collector_cycle() |
|||
end |
|||
end |
|||
|
@ -0,0 +1,76 @@ |
|||
|
|||
-- seed the random number generator with the current time |
|||
math.randomseed(os.clock()) |
|||
|
|||
-- https://stackoverflow.com/a/20177466/12464892 |
|||
--local A1, A2 = 727595, 798405 -- 5^17=D20*A1+A2 |
|||
--local D20, D40 = 1048576, 1099511627776 -- 2^20, 2^40 |
|||
--local X1, X2 = 0, 1 |
|||
--local function rand() |
|||
-- local U = X2*A2 |
|||
-- local V = (X1*A2 + X2*A1) % D20 |
|||
-- V = (V*D20 + U) % D40 |
|||
-- X1 = math.floor(V/D20) |
|||
-- X2 = V - X1*D20 |
|||
-- return V/D40 |
|||
--end |
|||
-- |
|||
--local SEED_BOUNDS = 2^20 - 1 |
|||
--math.randomseed = function(seed) |
|||
-- local v = math.clamp(math.abs(seed), 0, SEED_BOUNDS) |
|||
-- X1 = v |
|||
-- X2 = v + 1 |
|||
--end |
|||
|
|||
-- to enable allowing the random number generator's state to be restored post-load (game-deserialize), |
|||
-- we count the number of times we call math.random(), and on deserialize, seed the random |
|||
-- number generator, and then discard |count| calls. |
|||
local R = math.random |
|||
RANDOM_CALLS_COUNT = 0 |
|||
local function random(n, m) |
|||
RANDOM_CALLS_COUNT = RANDOM_CALLS_COUNT + 1 |
|||
|
|||
if n then |
|||
if m then |
|||
return R(n, m) |
|||
else |
|||
return R(n) |
|||
end |
|||
else |
|||
return R() |
|||
end |
|||
end |
|||
|
|||
-- whenever we refer to math.random, actually use the function 'random' above |
|||
math.random = random |
|||
|
|||
function g_octave_noise(x, y, num_octaves, seed) |
|||
local seed = seed or os.clock() |
|||
local noise = 0 |
|||
|
|||
for oct = 1, num_octaves do |
|||
local f = 1/4^oct |
|||
local l = 2^oct |
|||
local pos = vec2(x + seed, y + seed) |
|||
noise = noise + f * math.simplex(pos * l) |
|||
end |
|||
|
|||
return noise |
|||
end |
|||
|
|||
---- @TODO test, fix |
|||
--function poisson_knuth(lambda) |
|||
-- local e = 2.71828 |
|||
-- |
|||
-- local L = e^-lambda |
|||
-- local k = 0 |
|||
-- local p = 1 |
|||
-- |
|||
-- while p > L do |
|||
-- k = k + 1 |
|||
-- p = p * math.random() |
|||
-- end |
|||
-- |
|||
-- return k - 1 |
|||
--end |
|||
|
@ -0,0 +1,105 @@ |
|||
|
|||
local IMG_FILE_PREFIX = "res/img/" |
|||
|
|||
local function load_texture(filepath) |
|||
local path = IMG_FILE_PREFIX .. filepath |
|||
local status, texture = pcall(am.texture2d, path) |
|||
|
|||
if status then |
|||
return texture |
|||
else |
|||
log("failed to load texture at path: " .. path) |
|||
return am.texture2d(IMG_FILE_PREFIX .. "bagel.jpg") |
|||
end |
|||
end |
|||
|
|||
TEXTURES = { |
|||
-- note that in amulet, if you prefix paths with './', they fail to be found in the exported data.pak |
|||
WHITE = load_texture("white-texture.png"), |
|||
LOGO = load_texture("logo.png"), |
|||
GEM1 = load_texture("gem1.png"), |
|||
|
|||
SHADED_HEX = load_texture("shaded_hex.png"), |
|||
NEW_GAME_HEX = load_texture("newgamehex.png"), |
|||
SAVE_GAME_HEX = load_texture("savegamehex.png"), |
|||
LOAD_GAME_HEX = load_texture("loadgamehex.png"), |
|||
SETTINGS_HEX = load_texture("settingshex.png"), |
|||
MAP_EDITOR_HEX = load_texture("mapeditorhex.png"), |
|||
ABOUT_HEX = load_texture("abouthex.png"), |
|||
QUIT_HEX = load_texture("quithex.png"), |
|||
UNPAUSE_HEX = load_texture("unpausehex.png"), |
|||
MAIN_MENU_HEX = load_texture("mainmenuhex.png"), |
|||
|
|||
CURTAIN = load_texture("curtain1.png"), |
|||
|
|||
SOUND_ON1 = load_texture("sound-on.png"), |
|||
SOUND_OFF = load_texture("sound-off.png"), |
|||
|
|||
-- gui stuff |
|||
BUTTON1 = load_texture("button1.png"), |
|||
WIDER_BUTTON1 = load_texture("wider_button1.png"), |
|||
GEAR = load_texture("gear.png"), |
|||
|
|||
SELECT_BOX = load_texture("select_box.png"), |
|||
|
|||
-- tower stuff |
|||
TOWER_WALL = load_texture("tower_wall.png"), |
|||
TOWER_WALL_ICON = load_texture("tower_wall_icon.png"), |
|||
TOWER_GATTLER = load_texture("tower_gattler.png"), |
|||
TOWER_GATTLER_ICON = load_texture("tower_gattler_icon.png"), |
|||
TOWER_HOWITZER = load_texture("tower_howitzer.png"), |
|||
TOWER_HOWITZER_ICON = load_texture("tower_howitzer_icon.png"), |
|||
TOWER_REDEYE = load_texture("tower_redeye.png"), |
|||
TOWER_REDEYE_ICON = load_texture("tower_redeye_icon.png"), |
|||
TOWER_MOAT = load_texture("tower_moat.png"), |
|||
TOWER_MOAT_ICON = load_texture("tower_moat_icon.png"), |
|||
TOWER_RADAR = load_texture("tower_radar.png"), |
|||
TOWER_RADAR_ICON = load_texture("tower_radar_icon.png"), |
|||
TOWER_LIGHTHOUSE = load_texture("tower_lighthouse.png"), |
|||
TOWER_LIGHTHOUSE_ICON = load_texture("tower_lighthouse_icon.png"), |
|||
|
|||
-- mob stuff |
|||
MOB_BEEPER = load_texture("mob_beeper.png"), |
|||
MOB_SPOODER = load_texture("mob_spooder.png"), |
|||
MOB_VELKOOZ = load_texture("mob_velkooz.png"), |
|||
MOB_VELKOOZ1 = load_texture("mob_velkooz1.png"), |
|||
MOB_VELKOOZ2 = load_texture("mob_velkooz2.png"), |
|||
MOB_VELKOOZ3 = load_texture("mob_velkooz3.png"), |
|||
} |
|||
|
|||
function pack_texture_into_sprite( |
|||
texture, |
|||
width, |
|||
height, |
|||
color, |
|||
s1, |
|||
s2, |
|||
t1, |
|||
t2 |
|||
) |
|||
local width, height = width or texture.width, height or texture.height |
|||
|
|||
local sprite = am.sprite{ |
|||
texture = texture, |
|||
s1 = s1 or 0, s2 = s2 or 1, t1 = t1 or 0, t2 = t2 or 1, |
|||
x1 = 0, x2 = width, width = width, |
|||
y1 = 0, y2 = height, height = height |
|||
} |
|||
|
|||
if color then sprite.color = color end |
|||
|
|||
return sprite |
|||
end |
|||
|
|||
function update_sprite(sprite, texture, width, height, s1, t1, s2, t2) |
|||
local s1, t1, s2, t2 = s1 or 0, t1 or 0, s2 or 1, t2 or 1 |
|||
local width, height = width or texture.width, height or texture.height |
|||
|
|||
sprite.source = { |
|||
texture = texture, |
|||
s1 = s1, t1 = t1, s2 = s2, t2 = t2, |
|||
x1 = 0, x2 = width, width = width, |
|||
y1 = 0, y2 = height, height = height |
|||
} |
|||
end |
|||
|
Before Width: 2000 | Height: 2000 | Size: 716 KiB |
Before Width: 282 | Height: 290 | Size: 59 KiB After Width: 282 | Height: 290 | Size: 59 KiB |
Before Width: 820 | Height: 820 | Size: 165 KiB After Width: 820 | Height: 820 | Size: 165 KiB |
Before Width: 128 | Height: 128 | Size: 454 B After Width: 128 | Height: 128 | Size: 454 B |
Before Width: 1920 | Height: 1080 | Size: 238 KiB After Width: 1920 | Height: 1080 | Size: 238 KiB |
Before Width: 200 | Height: 200 | Size: 4.8 KiB After Width: 200 | Height: 200 | Size: 4.8 KiB |
Before Width: 980 | Height: 798 | Size: 62 KiB After Width: 980 | Height: 798 | Size: 62 KiB |
Before Width: 419 | Height: 483 | Size: 99 KiB After Width: 419 | Height: 483 | Size: 99 KiB |
Before Width: 282 | Height: 290 | Size: 50 KiB After Width: 282 | Height: 290 | Size: 50 KiB |
Before Width: 905 | Height: 483 | Size: 138 KiB After Width: 905 | Height: 483 | Size: 138 KiB |
Before Width: 282 | Height: 290 | Size: 59 KiB After Width: 282 | Height: 290 | Size: 59 KiB |
Before Width: 283 | Height: 290 | Size: 58 KiB After Width: 283 | Height: 290 | Size: 58 KiB |
Before Width: 638 | Height: 639 | Size: 6.1 KiB After Width: 638 | Height: 639 | Size: 6.1 KiB |
Before Width: 40 | Height: 40 | Size: 558 B After Width: 40 | Height: 40 | Size: 558 B |
Before Width: 450 | Height: 392 | Size: 26 KiB After Width: 450 | Height: 392 | Size: 26 KiB |
Before Width: 64 | Height: 64 | Size: 2.7 KiB After Width: 64 | Height: 64 | Size: 2.7 KiB |
Before Width: 64 | Height: 64 | Size: 1.9 KiB After Width: 64 | Height: 64 | Size: 1.9 KiB |
Before Width: 64 | Height: 64 | Size: 1.9 KiB After Width: 64 | Height: 64 | Size: 1.9 KiB |
Before Width: 64 | Height: 64 | Size: 1.8 KiB After Width: 64 | Height: 64 | Size: 1.8 KiB |
Before Width: 282 | Height: 290 | Size: 59 KiB After Width: 282 | Height: 290 | Size: 59 KiB |
Before Width: 282 | Height: 290 | Size: 57 KiB After Width: 282 | Height: 290 | Size: 57 KiB |
Before Width: 283 | Height: 290 | Size: 58 KiB After Width: 283 | Height: 290 | Size: 58 KiB |
Before Width: 128 | Height: 128 | Size: 404 B After Width: 128 | Height: 128 | Size: 404 B |
Before Width: 282 | Height: 290 | Size: 57 KiB After Width: 282 | Height: 290 | Size: 57 KiB |
Before Width: 253 | Height: 290 | Size: 49 KiB After Width: 253 | Height: 290 | Size: 49 KiB |
Before Width: 400 | Height: 20 | Size: 442 B After Width: 400 | Height: 20 | Size: 442 B |
Before Width: 980 | Height: 728 | Size: 28 KiB After Width: 980 | Height: 728 | Size: 28 KiB |
Before Width: 835 | Height: 653 | Size: 123 KiB After Width: 835 | Height: 653 | Size: 123 KiB |
Before Width: 64 | Height: 64 | Size: 335 B After Width: 64 | Height: 64 | Size: 335 B |
Before Width: 64 | Height: 64 | Size: 360 B After Width: 64 | Height: 64 | Size: 360 B |
Before Width: 64 | Height: 64 | Size: 292 B After Width: 64 | Height: 64 | Size: 292 B |
Before Width: 64 | Height: 64 | Size: 366 B After Width: 64 | Height: 64 | Size: 366 B |
Before Width: 64 | Height: 64 | Size: 796 B After Width: 64 | Height: 64 | Size: 796 B |
Before Width: 64 | Height: 64 | Size: 796 B After Width: 64 | Height: 64 | Size: 796 B |
Before Width: 137 | Height: 137 | Size: 1.4 KiB After Width: 137 | Height: 137 | Size: 1.4 KiB |
Before Width: 137 | Height: 137 | Size: 1.4 KiB After Width: 137 | Height: 137 | Size: 1.4 KiB |
Before Width: 300 | Height: 300 | Size: 30 KiB After Width: 300 | Height: 300 | Size: 30 KiB |
Before Width: 300 | Height: 300 | Size: 30 KiB After Width: 300 | Height: 300 | Size: 30 KiB |
Before Width: 64 | Height: 64 | Size: 637 B After Width: 64 | Height: 64 | Size: 637 B |
Before Width: 64 | Height: 64 | Size: 637 B After Width: 64 | Height: 64 | Size: 637 B |
Before Width: 126 | Height: 131 | Size: 1.4 KiB After Width: 126 | Height: 131 | Size: 1.4 KiB |
Before Width: 126 | Height: 131 | Size: 1.4 KiB After Width: 126 | Height: 131 | Size: 1.4 KiB |
Before Width: 282 | Height: 290 | Size: 59 KiB After Width: 282 | Height: 290 | Size: 59 KiB |
After Width: 32 | Height: 32 | Size: 117 B |
Before Width: 256 | Height: 128 | Size: 533 B After Width: 256 | Height: 128 | Size: 533 B |