Skip to content

Commit

Permalink
Merge branch 'docs' into 'master'
Browse files Browse the repository at this point in the history
docs: add ScalaDoc comments and README

See merge request dobesmic/bi-oop-ascii-art!11
  • Loading branch information
mishpajz committed Dec 10, 2023
2 parents d478447 + 44282dd commit 9e9b443
Show file tree
Hide file tree
Showing 63 changed files with 957 additions and 22 deletions.
41 changes: 41 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,44 @@
[![code coverage](https://gitlab.fit.cvut.cz/dobesmic/bi-oop-ascii-art/badges/master/coverage.svg)](https://gitlab.fit.cvut.cz/dobesmic/bi-oop-ascii-art/)

The idea of this project is to load images, translate them into ASCII ART images, optionally apply filters, and save them. (https://courses.fit.cvut.cz/BI-OOP/projects/ASCII-art.html)

## Usage

Run the application using `sbt run`.

### Importing images

- __`--image <path>`__ to import a image from a file. Currently _PNG_ and _JPEG_ formats are supported.
- __`--image-random`__ to create a random image (random lines in different directions), the size of the image will be between [100; 300] in both width and height.

### ASCII conversion table
A conversion table is used for converting between pixel value and ASCII character.

- __`--table <name>`__ to specify which predefined table to use. Current options are _bourke_ and _grayramp_.
- __`--custom-table <table>`__ to define custom table using a string of characters, with first characters corresponding to darkest pixels and last characters to lightest pixels. (e.g. `--custom-table "@#xw:. "` will map characters in "@#xw:. " to pixels values.)

If no table is specified, the default table is used (_bourke_).

### Exporting images
Images are currently exported in text form.

- __`--output-file <path>`__ to export the image into a text file.
- __`--output-console`__ to print the image into standard output.

Image can be exported multiple times.
### Filters

- __`--brightness <value>`__ to change the brightness of all pixels in a imported image by a given value. Value must be integer and can be positive or negative.
- __`--invert`__ to invert the color of image (black becomes white...).
- __`--flip <axis>`__ to flip the image along a given axis. The axis can be either _x_ or _y_.
- __`--scale <scale>`__ to scale the image by a given scale. This will change the amout of pixels in a image. The scale can be _0.25_, _1_ (identity) and _4_. (e.g. `--scale 0.25` will scale the image to 1/4 of its original size, meaning both dimensions of the image will be halved.)

The filters will be applied in the order they are passed to the application. Multiple filters of the same type can be applied.

## Example
Before:<br>
<img src="docs/resources/snowflake.png" alt="snowflake" width="400"/><br>
After:<br>
<img src="docs/resources/asciisnowflake.png" alt="asciisnowflake" width="400"/>

<sub><sup><a href="https://www.vecteezy.com/free-png/snowflake">Snowflake PNGs by Vecteezy</a></sub></sup>
Binary file added docs/resources/asciisnowflake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
196 changes: 196 additions & 0 deletions docs/resources/asciisnowflake.txt

Large diffs are not rendered by default.

Binary file added docs/resources/snowflake.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
22 changes: 21 additions & 1 deletion src/main/scala/App/ASCIIArtFacade.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,28 @@ import models.image.Image
import models.pixel.{ASCIIPixel, GrayscalePixel, Pixel}
import transformers.Transformer

import scala.util.Try
import scala.util.{Failure, Success, Try}

/**
* Facade for the standard ASCII Art process.
*
* Implements [[ASCIIArtProcessor]].
*
* The process is as follows:
* 1. use importer [[ImageImporter]] to import an [[Image]] of [[Pixel]]
* 2. use grayscaler [[Transformer]] to transform the [[Image]] to [[GrayscalePixel]]
* 3. use filter [[ImageFilter]] to perform changes on the [[Image]]
* 4. use asciier [[Transformer]] to transform the [[Image]] to [[ASCIIPixel]]
* 5. use exporter [[ImageExporter]] to export the [[Image]]
*
* @param importer image importer
* @param filter image filter
* @param exporter image exporter
* @param grayscaler image pixel to grayscale transformer
* @param asciier image grayscale to ascii transformer
*
* @return [[Success]] if all steps of the process were successful or [[Failure]]
*/
class ASCIIArtFacade(
val importer: ImageImporter[Pixel],
val filter: ImageFilter[GrayscalePixel],
Expand Down
12 changes: 11 additions & 1 deletion src/main/scala/App/ASCIIArtProcessor.scala
Original file line number Diff line number Diff line change
@@ -1,7 +1,17 @@
package App

import scala.util.Try
import scala.util.{Failure, Success, Try}

/**
* ASCII art processor.
*
*/
trait ASCIIArtProcessor {

/**
* Run the ASCII Art process.
*
* @return [[Success]] if process was successful or [[Failure]]
*/
def run(): Try[Unit]
}
2 changes: 0 additions & 2 deletions src/main/scala/App/Main.scala
Original file line number Diff line number Diff line change
Expand Up @@ -84,5 +84,3 @@ object Main extends App {
case Failure(exception) => throw exception
}
}

//TODO: - comments
63 changes: 63 additions & 0 deletions src/main/scala/App/commandline/ASCIIArtCommandLine.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ import utilities.SeqUtilities.{SeqTryExtensions, validateMaxSize, validateNonEmp

import scala.util.Try

/**
* Command line processor for the ASCII Art application.
*
* Parsers are used to parse command line arguments into elements.
* If any of the parsed elements fails, the method fails.
*
* The method fails with [[IllegalArgumentException]] if:
* - no importer is parsed
* - no exporter is parsed
* - more than one importer is parsed
* - more than one table is parsed
*
* The ASCII Art process supplied in appProvider is then run with parsed elements.
*
* @param importerParser parser for [[ImageImporter]] wrapped in [[Try]]
* @param filterParser parser for [[ImageFilter]] wrapped in [[Try]]
* @param exporterParser parser for [[ImageExporter]] wrapped in [[Try]]
* @param tableParser parser for [[ASCIITable]] wrapped in [[Try]]
* @param appProvider function that provides an [[ASCIIArtProcessor]] given the [[ImageImporter]], [[ImageFilter]], [[ImageExporter]] and [[ASCIITable]]
*/
class ASCIIArtCommandLine(
val importerParser: Parser[Try[ImageImporter[RGBAPixel]]],
val filterParser: Parser[Try[ImageFilter[GrayscalePixel]]],
Expand Down Expand Up @@ -57,6 +77,12 @@ object ASCIIArtCommandLine {
private type ExporterParseHandler =
ParseHandler[Try[ImageExporter[ASCIIPixel]]]
private type TableParseHandler = ParseHandler[Try[ASCIITable]]

/**
* Builder for [[ASCIIArtCommandLine]].
*
* @param appProvider function that provides an [[ASCIIArtProcessor]] given the [[ImageImporter]], [[ImageFilter]], [[ImageExporter]] and [[ASCIITable]]
*/
case class Builder(
appProvider: (
ImageImporter[RGBAPixel],
Expand All @@ -69,26 +95,63 @@ object ASCIIArtCommandLine {
private var exporterHandlers: Seq[ExporterParseHandler] = Seq.empty
private var tableHandlers: Seq[TableParseHandler] = Seq.empty

/**
* Add a [[ParseHandler]] that parses [[ImageImporter]] wrapped in [[Try]].
*
* This parser will be supplied to the built [[ASCIIArtCommandLine]].
*
* @param handler handler to add
* @return
*/
def withImporterHandler(handler: ImporterParseHandler): this.type = {
importerHandlers = importerHandlers.appended(handler)
this
}

/**
* Add a [[ParseHandler]] that parses [[ImageFilter]] wrapped in [[Try]].
*
* This parser will be supplied to the built [[ASCIIArtCommandLine]].
*
* @param handler handler to add
* @return
*/
def withFilterHandler(handler: FilterParseHandler): this.type = {
filterHandlers = filterHandlers.appended(handler)
this
}

/**
* Add a [[ParseHandler]] that parses [[ImageExporter]] wrapped in [[Try]].
*
* This parser will be supplied to the built [[ASCIIArtCommandLine]].
*
* @param handler handler to add
* @return
*/
def withExporterHandler(handler: ExporterParseHandler): this.type = {
exporterHandlers = exporterHandlers.appended(handler)
this
}

/**
* Add a [[ParseHandler]] that parses [[ASCIITable]] wrapped in [[Try]].
*
* This parser will be supplied to the built [[ASCIIArtCommandLine]].
*
* @param handler handler to add
* @return
*/
def withTableHandler(handler: TableParseHandler): this.type = {
tableHandlers = tableHandlers.appended(handler)
this
}

/**
* Build an [[ASCIIArtCommandLine]] out of the supplied [[ParseHandler]] and [[ASCIIArtProcessor]] provider.
*
* @return built [[ASCIIArtCommandLine]]
*/
def build(): ASCIIArtCommandLine =
new ASCIIArtCommandLine(
new Parser(importerHandlers),
Expand Down
12 changes: 12 additions & 0 deletions src/main/scala/App/commandline/parsers/Parser.scala
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,12 @@ import App.commandline.parsers.handlers.ParseHandler

import scala.annotation.tailrec

/**
* Parser.
*
* @tparam R type of the parsed item
* @param handlers sequence of handlers that parse arguments into items
*/
class Parser[R](private val handlers: Seq[ParseHandler[R]]) {

private val preparedHandlers = handlers.to(LazyList)
Expand All @@ -27,6 +33,12 @@ class Parser[R](private val handlers: Seq[ParseHandler[R]]) {
}
}

/**
* Parse arguments into elements.
*
* @param arguments arguments to parse
* @return parsed elements
*/
def parse(arguments: Seq[String]): Seq[R] =
parseRecursively(arguments, Seq.empty[R])
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
package App.commandline.parsers.handlers

/**
* Command [[ParseHandler]].
*
* Parses a simple command without a parameter.
*
* @param command command to parse
* @param item functions that returns the item when parsing succeeds
*/
final case class CommandParseHandler[T](command: String, item: () => T)
extends ParseHandler[T] {
override def handle(args: Seq[String]): Option[(Seq[String], T)] =
Expand Down
17 changes: 17 additions & 0 deletions src/main/scala/App/commandline/parsers/handlers/ParseHandler.scala
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
package App.commandline.parsers.handlers

/**
* Parse handler.
*
* @tparam T type of the parsed item
*/
trait ParseHandler[T] {

/**
* Attempt to parse the item.
*
* Only the arguments at the beginning are considered, if parsing
* does not succeed there, [[None]] is returned.
* Else, the remaining arguments (with the already parsed arguments removed)
* and the parsed item are returned.
*
* @param args sequence arguments to parse
* @return [[Some]] with the remaining arguments and the parsed item or [[None]]
*/
def handle(args: Seq[String]): Option[(Seq[String], T)]
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package App.commandline.parsers.handlers

/**
* Property [[ParseHandler]].
*
* Parses a property with a parameter.
* Parameter is considered to follow the property.
*
* @param command property to parse
* @param item function that accepts the property and returns the item when parsing succeeds
*/
final case class PropertyParseHandler[T](command: String, item: String => T)
extends ParseHandler[T] {
override def handle(args: Seq[String]): Option[(Seq[String], T)] =
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
package App.commandline.parsers.parametrizers

/**
* Parametrizer.
*
* Creates an item with a supplied provided parameter.
*
* @tparam T type of the parameterized item
*/
trait Parametrizer[T] {
def parametrize(parameter: String): T

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import filters.image.concrete.BrightenImageFilter

import scala.util.Try

/**
* [[Parametrizer]] for [[BrightenImageFilter]].
*
* Attempts to convert a [[String]] to a brightness value and create [[BrightenImageFilter]] with given value.
*
* Only numerical values are accepted.
*
*/
final case class BrightenImageFilterParametrizer()
extends Parametrizer[Try[BrightenImageFilter]] {
override def parametrize(parameter: String): Try[BrightenImageFilter] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,22 @@ package App.commandline.parsers.parametrizers.concrete

import App.commandline.parsers.parametrizers.Parametrizer
import filters.image.concrete.FlipImageFilter
import models.image.Image
import models.pixel.Pixel
import utilities.Axes

import scala.reflect.ClassTag
import scala.util.{Failure, Try}

/**
* [[Parametrizer]] for [[FlipImageFilter]].
*
* Attempts to convert a [[String]] to an axis and create [[FlipImageFilter]] with given value.
*
* Only 'x' and 'y' values are accepted.
*
* @tparam T type of the [[Pixel]] of the [[Image]] for which the filter should be created
*/
final case class FlipImageFilterParametrizer[T <: Pixel: ClassTag]()
extends Parametrizer[Try[FlipImageFilter[T]]] {
override def parametrize(parameter: String): Try[FlipImageFilter[T]] =
Expand All @@ -16,8 +26,9 @@ final case class FlipImageFilterParametrizer[T <: Pixel: ClassTag]()
case "x" => Axes.X
case "y" => Axes.Y
case _ =>
return Failure(new IllegalArgumentException(
"Unknown axis for flipping: acceptable values are 'x' or 'y'"))
return Failure(
new IllegalArgumentException(
"Unknown axis for flipping: acceptable values are 'x' or 'y'"))
}

FlipImageFilter[T](axis)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,21 @@ package App.commandline.parsers.parametrizers.concrete

import App.commandline.parsers.parametrizers.Parametrizer
import filters.image.concrete.{ScaleImageFilter, Scales}
import models.image.Image
import models.pixel.Pixel

import scala.reflect.ClassTag
import scala.util.{Failure, Try}

/**
* [[Parametrizer]] for [[ScaleImageFilter]].
*
* Attempts to convert a [[String]] to a scale and create [[ScaleImageFilter]] with given value.
*
* Only '0.25', '1' and '4' values are accepted.
*
* @tparam T type of the [[Pixel]] of the [[Image]] for which the filter should be created
*/
final case class ScaleImageFilterParametrizer[T <: Pixel: ClassTag]()
extends Parametrizer[Try[ScaleImageFilter[T]]] {
override def parametrize(parameter: String): Try[ScaleImageFilter[T]] =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,12 @@ import registries.models.asciitable.ASCIITableRegistry

import scala.util.{Failure, Try}

/**
* [[Parametrizer]] for [[ASCIITable]].
*
* Attempts to convert a [[String]] to a name of [[ASCIITable]] and retrieve the [[ASCIITable]] from a [[Registry]].
*
*/
final case class TableSelectionParametrizer(
registry: Registry[String, ASCIITable] = ASCIITableRegistry)
extends Parametrizer[Try[ASCIITable]] {
Expand Down
Loading

0 comments on commit 9e9b443

Please sign in to comment.