Optimize finalized surface artwork
This commit is contained in:
parent
82938d3d57
commit
f2239d248a
14 changed files with 1254 additions and 305 deletions
284
drawing/surface_drawing_cell_buffer.gd
Normal file
284
drawing/surface_drawing_cell_buffer.gd
Normal file
|
|
@ -0,0 +1,284 @@
|
|||
# SPDX-License-Identifier: GPL-3.0-or-later
|
||||
class_name SurfaceDrawingCellBuffer
|
||||
extends RefCounted
|
||||
|
||||
const ENCODING := "palette_author_planes_v1"
|
||||
|
||||
var width: int = 0
|
||||
var height: int = 0
|
||||
|
||||
var _color_indices := PackedByteArray()
|
||||
var _author_indices := PackedInt32Array()
|
||||
var _participants: Array[String] = []
|
||||
var _participant_slots: Dictionary[String, int] = {}
|
||||
var _author_masks: Dictionary[int, PackedByteArray] = {}
|
||||
var _painted_count: int = 0
|
||||
|
||||
|
||||
func configure_from_state(
|
||||
data: Dictionary,
|
||||
already_validated: bool = false,
|
||||
) -> bool:
|
||||
if not already_validated and not SurfaceDrawingProtocol.validate_canvas_state(data):
|
||||
return false
|
||||
width = int(data["width"])
|
||||
height = int(data["height"])
|
||||
_reset_planes()
|
||||
for value: Variant in data.get("participant_fingerprints", []):
|
||||
_add_participant(str(value))
|
||||
_add_participant(str(data.get("creator_fingerprint", "")))
|
||||
if str(data.get("cell_encoding", "")) == ENCODING:
|
||||
return _configure_packed(
|
||||
data.get("cell_colors", PackedByteArray()),
|
||||
data.get("cell_authors", PackedInt32Array()),
|
||||
)
|
||||
for value: Variant in data.get("cells", []):
|
||||
if typeof(value) != TYPE_DICTIONARY or not _apply_edit(value):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func apply_edits(edits: Array) -> bool:
|
||||
for value: Variant in edits:
|
||||
if typeof(value) != TYPE_DICTIONARY or not _apply_edit(value):
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func ensure_participants(values: Array) -> bool:
|
||||
for value: Variant in values:
|
||||
if _add_participant(str(value)) <= 0:
|
||||
return false
|
||||
return true
|
||||
|
||||
|
||||
func get_cell(x: int, y: int) -> Dictionary:
|
||||
var key: int = _key(x, y)
|
||||
if key < 0 or _color_indices[key] == 0:
|
||||
return {}
|
||||
var author_slot: int = _author_indices[key]
|
||||
return {
|
||||
"x": x,
|
||||
"y": y,
|
||||
"color_id": str(
|
||||
SurfaceDrawingPalette.id_for_palette_index(_color_indices[key])
|
||||
),
|
||||
"author_fingerprint": (
|
||||
_participants[author_slot - 1]
|
||||
if author_slot > 0 and author_slot <= _participants.size()
|
||||
else ""
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
func get_cell_value(x: int, y: int) -> Dictionary:
|
||||
var cell: Dictionary = get_cell(x, y)
|
||||
if cell.is_empty():
|
||||
return {}
|
||||
return {
|
||||
"color_id": cell["color_id"],
|
||||
"author_fingerprint": cell["author_fingerprint"],
|
||||
}
|
||||
|
||||
|
||||
func get_painted_count() -> int:
|
||||
return _painted_count
|
||||
|
||||
|
||||
func get_participants() -> Array[String]:
|
||||
return _participants.duplicate()
|
||||
|
||||
|
||||
func get_author_cell_keys(fingerprint: String) -> PackedInt32Array:
|
||||
var slot: int = int(_participant_slots.get(fingerprint, 0))
|
||||
var mask: PackedByteArray = _author_masks.get(slot, PackedByteArray())
|
||||
var result := PackedInt32Array()
|
||||
if slot <= 0 or mask.is_empty():
|
||||
return result
|
||||
for key: int in width * height:
|
||||
if _mask_has(mask, key):
|
||||
result.append(key)
|
||||
return result
|
||||
|
||||
|
||||
func to_cells() -> Array[Dictionary]:
|
||||
var result: Array[Dictionary] = []
|
||||
for key: int in width * height:
|
||||
if _color_indices[key] == 0:
|
||||
continue
|
||||
result.append(get_cell(key % width, floori(float(key) / float(width))))
|
||||
return result
|
||||
|
||||
|
||||
func to_compact_fields() -> Dictionary:
|
||||
return {
|
||||
"cell_encoding": ENCODING,
|
||||
"cell_colors": _color_indices.duplicate(),
|
||||
"cell_authors": _author_indices.duplicate(),
|
||||
"participant_fingerprints": get_participants(),
|
||||
}
|
||||
|
||||
|
||||
func to_rgba8_data(hidden: bool = false) -> PackedByteArray:
|
||||
var result := PackedByteArray()
|
||||
result.resize(width * height * 4)
|
||||
# Match Image.fill(Color.TRANSPARENT): transparent texels retain white RGB,
|
||||
# preventing dark fringes if filtering is ever changed by a platform driver.
|
||||
for pixel_index: int in width * height:
|
||||
var offset: int = pixel_index * 4
|
||||
result[offset] = 255
|
||||
result[offset + 1] = 255
|
||||
result[offset + 2] = 255
|
||||
if hidden:
|
||||
return result
|
||||
for key: int in width * height:
|
||||
var palette_index: int = _color_indices[key]
|
||||
if palette_index == 0:
|
||||
continue
|
||||
var x: int = key % width
|
||||
var y: int = floori(float(key) / float(width))
|
||||
var image_key: int = (height - 1 - y) * width + x
|
||||
var offset: int = image_key * 4
|
||||
var color: Color = SurfaceDrawingPalette.color_for_palette_index(
|
||||
palette_index
|
||||
)
|
||||
result[offset] = roundi(color.r * 255.0)
|
||||
result[offset + 1] = roundi(color.g * 255.0)
|
||||
result[offset + 2] = roundi(color.b * 255.0)
|
||||
result[offset + 3] = roundi(color.a * 255.0)
|
||||
return result
|
||||
|
||||
|
||||
func estimated_storage_bytes() -> int:
|
||||
var masks_size: int = 0
|
||||
for mask: PackedByteArray in _author_masks.values():
|
||||
masks_size += mask.size()
|
||||
return _color_indices.size() + _author_indices.size() * 4 + masks_size
|
||||
|
||||
|
||||
func _configure_packed(colors: Variant, authors: Variant) -> bool:
|
||||
if (
|
||||
typeof(colors) != TYPE_PACKED_BYTE_ARRAY
|
||||
or typeof(authors) != TYPE_PACKED_INT32_ARRAY
|
||||
):
|
||||
return false
|
||||
var packed_colors: PackedByteArray = colors
|
||||
var packed_authors: PackedInt32Array = authors
|
||||
if (
|
||||
packed_colors.size() != width * height
|
||||
or packed_authors.size() != width * height
|
||||
):
|
||||
return false
|
||||
_color_indices = packed_colors.duplicate()
|
||||
_author_indices = packed_authors.duplicate()
|
||||
_painted_count = 0
|
||||
for key: int in width * height:
|
||||
var color_index: int = _color_indices[key]
|
||||
var author_slot: int = _author_indices[key]
|
||||
if color_index == 0:
|
||||
if author_slot != 0:
|
||||
return false
|
||||
continue
|
||||
if (
|
||||
color_index > SurfaceDrawingPalette.COLORS.size()
|
||||
or author_slot <= 0
|
||||
or author_slot > _participants.size()
|
||||
):
|
||||
return false
|
||||
_painted_count += 1
|
||||
_set_author_mask_bit(author_slot, key, true)
|
||||
return true
|
||||
|
||||
|
||||
func _apply_edit(value: Dictionary) -> bool:
|
||||
var x: int = int(value.get("x", -1))
|
||||
var y: int = int(value.get("y", -1))
|
||||
var key: int = _key(x, y)
|
||||
if key < 0:
|
||||
return false
|
||||
var color_id := StringName(str(value.get("color_id", "")))
|
||||
var next_color_index: int = (
|
||||
SurfaceDrawingPalette.palette_index_for_id(color_id)
|
||||
if not color_id.is_empty()
|
||||
else 0
|
||||
)
|
||||
if not color_id.is_empty() and next_color_index <= 0:
|
||||
return false
|
||||
var next_author_slot: int = 0
|
||||
if next_color_index > 0:
|
||||
next_author_slot = _add_participant(
|
||||
str(value.get("author_fingerprint", ""))
|
||||
)
|
||||
if next_author_slot <= 0:
|
||||
return false
|
||||
var previous_color_index: int = _color_indices[key]
|
||||
var previous_author_slot: int = _author_indices[key]
|
||||
if previous_color_index == next_color_index and previous_author_slot == next_author_slot:
|
||||
return true
|
||||
if previous_author_slot > 0:
|
||||
_set_author_mask_bit(previous_author_slot, key, false)
|
||||
if previous_color_index == 0 and next_color_index > 0:
|
||||
_painted_count += 1
|
||||
elif previous_color_index > 0 and next_color_index == 0:
|
||||
_painted_count -= 1
|
||||
_color_indices[key] = next_color_index
|
||||
_author_indices[key] = next_author_slot
|
||||
if next_author_slot > 0:
|
||||
_set_author_mask_bit(next_author_slot, key, true)
|
||||
return true
|
||||
|
||||
|
||||
func _reset_planes() -> void:
|
||||
_color_indices.resize(width * height)
|
||||
_color_indices.fill(0)
|
||||
_author_indices.resize(width * height)
|
||||
_author_indices.fill(0)
|
||||
_participants.clear()
|
||||
_participant_slots.clear()
|
||||
_author_masks.clear()
|
||||
_painted_count = 0
|
||||
|
||||
|
||||
func _add_participant(fingerprint: String) -> int:
|
||||
var existing: int = int(_participant_slots.get(fingerprint, 0))
|
||||
if existing > 0:
|
||||
return existing
|
||||
if not NetworkIdentityCrypto.valid_fingerprint(fingerprint):
|
||||
return 0
|
||||
if _participants.size() >= SurfaceDrawingProtocol.MAX_PARTICIPANTS:
|
||||
return 0
|
||||
_participants.append(fingerprint)
|
||||
var slot: int = _participants.size()
|
||||
_participant_slots[fingerprint] = slot
|
||||
return slot
|
||||
|
||||
|
||||
func _set_author_mask_bit(slot: int, key: int, enabled: bool) -> void:
|
||||
if slot <= 0 or key < 0:
|
||||
return
|
||||
var mask: PackedByteArray = _author_masks.get(slot, PackedByteArray())
|
||||
var required_size: int = ceili(float(width * height) / 8.0)
|
||||
if mask.size() != required_size:
|
||||
mask.resize(required_size)
|
||||
var byte_index: int = floori(float(key) / 8.0)
|
||||
var bit: int = 1 << (key % 8)
|
||||
if enabled:
|
||||
mask[byte_index] = mask[byte_index] | bit
|
||||
else:
|
||||
mask[byte_index] = mask[byte_index] & (~bit & 0xff)
|
||||
_author_masks[slot] = mask
|
||||
|
||||
|
||||
func _mask_has(mask: PackedByteArray, key: int) -> bool:
|
||||
var byte_index: int = floori(float(key) / 8.0)
|
||||
return (
|
||||
byte_index >= 0
|
||||
and byte_index < mask.size()
|
||||
and (mask[byte_index] & (1 << (key % 8))) != 0
|
||||
)
|
||||
|
||||
|
||||
func _key(x: int, y: int) -> int:
|
||||
if x < 0 or x >= width or y < 0 or y >= height:
|
||||
return -1
|
||||
return y * width + x
|
||||
Loading…
Add table
Add a link
Reference in a new issue