Skip to content

Commit

Permalink
feat: API to render a single section of the board (#56)
Browse files Browse the repository at this point in the history
* feat: section render

* adding missing tests

* missing test

* missing test

* missing tests

* add missing tests

---------

Co-authored-by: Jaime <52668514+jsgalarraga@users.noreply.github.com>
  • Loading branch information
erickzanardo and jsgalarraga authored Mar 12, 2024
1 parent 15b2f79 commit 1015e03
Show file tree
Hide file tree
Showing 9 changed files with 667 additions and 3 deletions.
Binary file added api/packages/board_renderer/assets/letters.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
132 changes: 129 additions & 3 deletions api/packages/board_renderer/lib/src/board_renderer.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:math' as math;
import 'dart:typed_data';
import 'package:game_domain/game_domain.dart';
import 'package:http/http.dart' as http;
import 'package:image/image.dart' as img;

/// A function that creates a command to execute.
Expand All @@ -26,6 +27,31 @@ typedef DrawRect = img.Image Function(
img.Channel maskChannel,
});

/// A function that composites an image.
typedef CompositeImage = img.Image Function(
img.Image dst,
img.Image src, {
int? dstX,
int? dstY,
int? dstW,
int? dstH,
int? srcX,
int? srcY,
int? srcW,
int? srcH,
img.BlendMode blend,
bool linearBlend,
bool center,
img.Image? mask,
img.Channel maskChannel,
});

/// A function that makes a GET request.
typedef GetCall = Future<http.Response> Function(Uri uri);

/// A function that decodes a PNG image.
typedef DecodePng = img.Image? Function(Uint8List data);

/// {@template board_renderer_failure}
/// Exception thrown when a board rendering fails.
/// {@endtemplate}
Expand All @@ -49,19 +75,28 @@ class BoardRenderer {
CreateCommand createCommand = img.Command.new,
CreateImage createImage = img.Image.new,
DrawRect drawRect = img.drawRect,
CompositeImage compositeImage = img.compositeImage,
DecodePng decodePng = img.decodePng,
GetCall get = http.get,
}) : _createCommand = createCommand,
_createImage = createImage,
_drawRect = drawRect;
_drawRect = drawRect,
_compositeImage = compositeImage,
_decodePng = decodePng,
_get = get;

final CreateCommand _createCommand;
final CreateImage _createImage;
final DrawRect _drawRect;
final CompositeImage _compositeImage;
final DecodePng _decodePng;

/// The size of each cell in the board.
static const cellSize = 4;
final GetCall _get;

/// Renders the full board in a single image.
Future<Uint8List> renderBoard(List<Word> words) async {
/// The size of each cell in the board when rendering in full size.
const cellSize = 4;
var minPositionX = 0;
var minPositionY = 0;

Expand Down Expand Up @@ -143,4 +178,95 @@ class BoardRenderer {

return outputBytes;
}

Future<Uint8List> _getFile(Uri uri) async {
final response = await _get(uri);

if (response.statusCode != 200) {
throw BoardRendererFailure('Failed to get image from $uri');
}

return response.bodyBytes;
}

/// Renders a section of the board in an image.
Future<Uint8List> renderSection(BoardSection section) async {
final words = section.words;

const cellSize = 40;

var maxPositionX = 0;
var maxPositionY = 0;

for (final word in words) {
final x = word.position.x - section.position.x * section.size;
final y = word.position.y - section.position.y * section.size;

final sizeX = word.axis == Axis.horizontal ? word.answer.length : 1;
final sizeY = word.axis == Axis.vertical ? word.answer.length : 1;

maxPositionX = math.max(maxPositionX, x + sizeX);
maxPositionY = math.max(maxPositionY, y + sizeY);
}

final totalWidth = maxPositionX * cellSize;
final totalHeight = maxPositionY * cellSize;

final image = _createImage(
width: totalWidth,
height: totalHeight,
);

const url = 'http://127.0.0.1:8080/assets/letters.png';
final textureImage = await _getFile(Uri.parse(url));

final texture = _decodePng(textureImage);
if (texture == null) {
throw BoardRendererFailure('Failed to load the texture');
}

for (final word in words) {
final x = word.position.x - section.position.x * section.size;
final y = word.position.y - section.position.y * section.size;

final position = (x, y);

final wordCharacters = word.answer.split('');

for (var c = 0; c < wordCharacters.length; c++) {
final char = wordCharacters.elementAt(c).toUpperCase();
final charIndex = char.codeUnitAt(0) - 65;

_compositeImage(
image,
texture,
dstX: word.axis == Axis.horizontal
? (position.$1 + c) * cellSize
: position.$1 * cellSize,
dstY: word.axis == Axis.vertical
? (position.$2 + c) * cellSize
: position.$2 * cellSize,
dstW: cellSize,
dstH: cellSize,
srcX: charIndex * cellSize,
srcY: 0,
srcW: cellSize,
srcH: cellSize,
);
}
}

final createdCommand = _createCommand()
..image(image)
..encodePng();

await createdCommand.execute();

final outputBytes = createdCommand.outputBytes;
if (outputBytes == null) {
throw BoardRendererFailure('Failed to render the section');
}

return outputBytes;
}
}
1 change: 1 addition & 0 deletions api/packages/board_renderer/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ environment:
dependencies:
game_domain:
path: ../game_domain
http: ^1.2.1
image: ^4.1.7

dev_dependencies:
Expand Down
Loading

0 comments on commit 1015e03

Please sign in to comment.