Skip to content

Commit 726b299

Browse files
committed
merge: Gold and Stone themes
2 parents b7dac43 + ce11388 commit 726b299

101 files changed

Lines changed: 33630 additions & 9 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

docs/logo/LOGO_DESIGN.md

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,24 @@ The `variants` folder contains the Admincraft logo and its alternative versions.
3737
3838
- App icons for all platforms will be updated and applied the next time the app is built.
3939
40+
### Recoloured Variants
41+
42+
Several Minecraft blocks are the same speckled stone in a different colour, so
43+
a variant for one of those does not need drawing from scratch.
44+
[recolor_variant.py](recolor_variant.py) builds one by mapping the brightness of
45+
`dirt.png`'s texture onto a new pair of colours, leaving the prompt glyph alone.
46+
`gold.png` and `stone.png` are made this way; add a ramp to `RAMPS` and rerun it
47+
to add another:
48+
49+
```
50+
cd docs\logo
51+
pip install -r requirements.txt
52+
python recolor_variant.py
53+
```
54+
55+
Blocks whose pattern differs from dirt, and anything with a face on it, still
56+
need to be drawn by hand as above.
57+
4058
### Technical Details
4159
4260
- The Python scripts upscale the logo to ensure suitability for various sizes and formats across platforms, while maintaining the crispness of pixel art.

docs/logo/recolor_variant.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
"""Builds a logo variant by recolouring the block texture of an existing one.
2+
3+
Several Minecraft blocks are the same speckled stone in a different colour, so
4+
a variant for one of those is a recolour rather than a new drawing. The prompt
5+
glyph is left alone: it is near-grey, and the texture is not, which is enough
6+
to tell them apart without hand-marking pixels.
7+
8+
cd docs/logo
9+
python recolor_variant.py
10+
11+
Writes into variants/. Existing files are overwritten, so it is safe to rerun
12+
after changing a ramp.
13+
"""
14+
15+
import os
16+
17+
from PIL import Image
18+
19+
SOURCE = os.path.join("variants", "dirt.png")
20+
21+
# Darkest and lightest tone of each block. The shades in between are taken from
22+
# the source's own brightness, which is what keeps the speckle pattern.
23+
RAMPS = {
24+
# Gold is a flatter block in game, so its ramp is deliberately short: the
25+
# same speckle at full strength reads as sand.
26+
"gold": ((0xC2, 0x9E, 0x1A), (0xFA, 0xF0, 0x62)),
27+
# Stone is speckled exactly like this, so it takes the full contrast.
28+
"stone": ((0x5E, 0x5E, 0x5E), (0xA8, 0xA8, 0xA8)),
29+
}
30+
31+
32+
def luminance(pixel):
33+
r, g, b = pixel[:3]
34+
return 0.299 * r + 0.587 * g + 0.114 * b
35+
36+
37+
def is_texture(pixel):
38+
"""True for block pixels, false for the glyph drawn on top of them.
39+
40+
The glyph is white or near-grey; every texture tone is saturated, so the
41+
spread between the channels separates the two cleanly.
42+
"""
43+
r, g, b = pixel[:3]
44+
return max(r, g, b) - min(r, g, b) > 25
45+
46+
47+
def recolor(image, dark, light):
48+
# Read through tobytes rather than getdata, which Pillow has deprecated in
49+
# favour of a method too new to rely on here.
50+
raw = image.tobytes()
51+
pixels = [tuple(raw[i:i + 4]) for i in range(0, len(raw), 4)]
52+
texture = [p for p in pixels if is_texture(p)]
53+
low = min(luminance(p) for p in texture)
54+
high = max(luminance(p) for p in texture)
55+
span = high - low or 1
56+
57+
out = []
58+
for pixel in pixels:
59+
if not is_texture(pixel):
60+
out.append(pixel)
61+
continue
62+
t = (luminance(pixel) - low) / span
63+
out.append(
64+
(
65+
round(dark[0] + (light[0] - dark[0]) * t),
66+
round(dark[1] + (light[1] - dark[1]) * t),
67+
round(dark[2] + (light[2] - dark[2]) * t),
68+
pixel[3],
69+
)
70+
)
71+
72+
return Image.frombytes(
73+
"RGBA", image.size, bytes(channel for pixel in out for channel in pixel)
74+
)
75+
76+
77+
def main():
78+
source = Image.open(SOURCE).convert("RGBA")
79+
for name, (dark, light) in RAMPS.items():
80+
path = os.path.join("variants", f"{name}.png")
81+
recolor(source, dark, light).save(path)
82+
print(f"wrote {path}")
83+
source.close()
84+
85+
86+
if __name__ == "__main__":
87+
main()

docs/logo/variants/gold.png

420 Bytes
Loading

docs/logo/variants/stone.png

403 Bytes
Loading

lib/models/app_theme.dart

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,28 @@ enum AppTheme {
4343
description: 'Soft and rosy',
4444
seedColor: Color(0xFFD2536B),
4545
logoAsset: 'docs/logo/variants/pig.png',
46+
),
47+
// The last warm hue with room left. It is nearer yellow than the metal
48+
// usually is, because a redder gold collapses into Dirt once Material
49+
// darkens it for light mode, and it takes the vibrant variant for the same
50+
// reason Creeper does.
51+
gold(
52+
label: 'Gold',
53+
description: 'Bright and precious',
54+
seedColor: Color(0xFFF9EC4E),
55+
logoAsset: 'docs/logo/variants/gold.png',
56+
variant: DynamicSchemeVariant.vibrant,
57+
),
58+
// Distinct by having no hue at all rather than by finding a free one, which
59+
// is also the point of it: something quiet to work against. Blue was the
60+
// obvious alternative and does not work, because Diamond and Obsidian sit
61+
// either side of it and leave no room.
62+
stone(
63+
label: 'Stone',
64+
description: 'Quiet and neutral',
65+
seedColor: Color(0xFF7A7A7A),
66+
logoAsset: 'docs/logo/variants/stone.png',
67+
variant: DynamicSchemeVariant.neutral,
4668
);
4769

4870
final String label;

lib/views/preferences_view.dart

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -86,11 +86,17 @@ class _PreferencesViewState extends State<PreferencesView> {
8686
const SizedBox(height: 8),
8787
LayoutBuilder(
8888
builder: (context, constraints) {
89-
final columns = constraints.maxWidth >= 720
90-
? 5
91-
: constraints.maxWidth >= 440
92-
? 3
93-
: 2;
89+
// Every breakpoint keeps a tile at roughly 100px or
90+
// more, which is what the longest label needs. The
91+
// icon stays 32px: it is 16x16 pixel art, so only
92+
// multiples of 16 scale without blurring it.
93+
final columns = constraints.maxWidth >= 880
94+
? 8
95+
: constraints.maxWidth >= 720
96+
? 6
97+
: constraints.maxWidth >= 440
98+
? 4
99+
: 3;
94100
const spacing = 8.0;
95101
final width =
96102
(constraints.maxWidth - spacing * (columns - 1)) /
@@ -289,23 +295,24 @@ class _ThemeChoice extends StatelessWidget {
289295
key: ValueKey('app-theme-${theme.name}'),
290296
onTap: onTap,
291297
child: Padding(
292-
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 10),
298+
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 8),
293299
child: Column(
294300
children: [
295301
Image.asset(
296302
theme.logoAsset,
303+
// A multiple of 16 keeps the pixel grid exact.
297304
width: 32,
298305
height: 32,
299306
fit: BoxFit.fill,
300307
filterQuality: FilterQuality.none,
301308
isAntiAlias: false,
302309
),
303-
const SizedBox(height: 6),
310+
const SizedBox(height: 4),
304311
Text(
305312
theme.label,
306313
maxLines: 1,
307314
overflow: TextOverflow.ellipsis,
308-
style: Theme.of(context).textTheme.labelLarge,
315+
style: Theme.of(context).textTheme.labelMedium,
309316
),
310317
],
311318
),

pubspec.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,9 @@ flutter:
7575
- docs/logo/variants/diamond.png
7676
- docs/logo/variants/obsidian_glow.png
7777
- docs/logo/variants/pig.png
78+
# Recoloured from dirt.png by docs/logo/recolor_variant.py.
79+
- docs/logo/variants/gold.png
80+
- docs/logo/variants/stone.png
7881
# Item icons from mcicons (MIT), named after Minecraft identifiers.
7982
# See the credits in README.md.
8083
- assets/mcicons/

0 commit comments

Comments
 (0)