Skip to content

Commit

Permalink
feat: Semantic Visual Emphasis Measure #54
Browse files Browse the repository at this point in the history
  • Loading branch information
sergiomrebelo committed Jul 26, 2023
1 parent 2361025 commit c91fdf4
Show file tree
Hide file tree
Showing 6 changed files with 129 additions and 40 deletions.
124 changes: 103 additions & 21 deletions src/@evoposter/evaluator/src/metrics/VisualSemantics.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -9,48 +9,133 @@
* return the mean of difference between all textboxes,
* a value between 1 (good) and 0 (bad)
*
* two modes: MIN and DIF
* MIN takes into account that the most important parts
* are typeset in the min value possible in the container
* DIF: take into account only the difference between the parts
* selects a style and compare based on the distribution of emotions.
*
*
*
* Sérgio M. Rebelo
* CDV lab. (CMS, CISUC, Portugal)
* srebelo[at]dei.uc.pt
*
* v1.0.0 November 2023
*/
import {arrMax, arrMean, arrMin, constraint, map} from "../utils.js";
import {arrMax, arrMean, arrMin, arrSum, constraint, map} from "../utils.js";

// by tradition, use more that a method to emphasize the text is considered typecrime
// this method enables turn on/off this, using the param allowMultiple
export const compute = (textboxes, dist, allowMultiple = false) => {
const fontWeight = 1-checkDifferenceVariableFeature(textboxes.map((b) => b["weight"]), dist.map((e) => e[3]));
const fontStretch = 1-checkDifferenceVariableFeature(textboxes.map((b) => b["font-stretch"]), dist.map((e) => e[3]));
let MIN_RANGE = 50;
let THRESHOLD_VALID = 0.2;

// console.log (`fontWeight=`, fontWeight);
// console.log (`fontStretch=`, fontStretch);

// type design
// by tradition, use more that a method to emphasize
// the text is considered "typecrime".
// this method enables turn on/off this feature
// by using the param allowMultiple
export const compute = (textboxes, dist, noCurrentTypefaces = 1, allowMultiple = true, weights = [0.4, 0.3, 0.3]) => {
// if textboxes size is 1 ---> 1
const perDist = dist.map((e) => e[3]);

const fontWeight = checkDifferenceVariableFeature(textboxes.map((b) => b["weight"]), perDist);
const fontStretch = checkDifferenceVariableFeature(textboxes.map((b) => b["font-stretch"]), perDist);
let typefaceDesign = noCurrentTypefaces > 1 ? (checkDifferenceUniqueFeatures(textboxes.map((b) => b["font-stretch"]), perDist)) : 0;

// way of combine and only checks one
return fontWeight;
let res = [fontWeight, fontStretch, typefaceDesign];
let weightedRes = res.map((x,i) => x*weights[i]);

let value;

if (!allowMultiple) {
// if not allowed multiple emphasis
// we check what fields are active
// and penalty when there is active fields
let active = res.map((r) => r > THRESHOLD_VALID);
let c = 0;
for (let a of active) {
if (a) {
c++;
}
}
value = arrMax(res)/c;
} else {
value = arrSum(weightedRes);
}

return value;
}

const checkDifferenceVariableFeature = (currentFeatures, dist) => {
// check the levels
const checkDifferenceUniqueFeatures = (currentFeatures, dist) => {
// available semantic level
const uniqueValues = dist.filter((value, index, array) => array.indexOf(value) === index);
const target = [];

// define the target typeface for each semantic level
for (let uq of uniqueValues) {
for (let i=0; i<dist.length; i++) {
if (dist[i] === uq) {
target.push(currentFeatures[i]);
break;
}
}
}

// check if the target has duplicates
const duplicates = target.filter((item, index) => target.indexOf(item) !== index);

let value = 1;
// if there is duplicate in levels
// (if yes, difference is max)
if (!duplicates.length >= 1) {
// count the amount of tb not in the same typeface
let c = 0;
// for each typeface
for (let i in dist) {
let level = dist[i];
let currentValue = currentFeatures[i];
// get unique index of current semantic level
let index = uniqueValues.indexOf(level);
// get target value
let targetValue = target[index];
if (currentValue !== targetValue) {
// if not the same as target
c++;
}
}
// map value to a value between 0 (no difference) and 1 (max difference)
value = map(c, 0, currentFeatures.length, 0, 1);
}

return value;
}

const checkDifferenceVariableFeature = (currentFeatures, dist, mode = `DIF`) => {
if (mode !== `MIN` && mode !== `DIF`) {
mode = `DIF`;
}
// max feature range
const maxFeature = arrMax(currentFeatures);
const minFeature = arrMin(currentFeatures);
let range = Math.abs(maxFeature - minFeature);
if (range < MIN_RANGE) {
return 1;
}

// semantic data range
const maxSemantic = arrMax(dist);
const minSemantic = arrMin(dist);

// selects a style used in the first min semantic textbox
let def = 0;
for (let i in dist) {
if (dist[i] === minSemantic) {
def = currentFeatures[i];
break;
// consider the current variable minimum
let def = minFeature;
if (mode === `DIF`) {
// selects a style used in the first min semantic textbox
for (let i in dist) {
if (dist[i] === minSemantic) {
// consider the difference
def = currentFeatures[i];
break;
}
}
}

Expand All @@ -66,10 +151,7 @@ const checkDifferenceVariableFeature = (currentFeatures, dist) => {
const current = [];
for (let i in currentFeatures) {
let w = currentFeatures[i];
// consider only the difference
let currentDistance = Math.abs(w - def);
// consider the variable scale
// let currentDistance = Math.abs(w - minFeature);
let dif = Math.abs(currentDistance - target[i]);
current.push(dif);
}
Expand Down
4 changes: 2 additions & 2 deletions src/client/Params.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@ export class Params {
static evolution = {
popSize: 50,
noGen: 400,
crossoverProb: 0.75,
mutationProb: 0.30,
crossoverProb: 0.90,
mutationProb: 0.10,
eliteSize: 1
}

Expand Down
11 changes: 9 additions & 2 deletions src/client/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import Population from "./controllers/Population.js";
import 'bootstrap/scss/bootstrap.scss';
import './main.css';


window.preload = () => {}

window.setup = () => {
Expand Down Expand Up @@ -46,6 +47,8 @@ window.keyPressed = () => {
// }
}



export class App extends LitElement {
static properties = {
screen: 0,
Expand All @@ -60,8 +63,8 @@ export class App extends LitElement {
this.evolving = false;

const fonts = this.#getAvailableTypefaces();

// evolution controllers
//
this.config = {
evo: {
popSize: Params.evolution.popSize,
Expand Down Expand Up @@ -135,7 +138,11 @@ export class App extends LitElement {
for (let font of Array.from(document.fonts)) {
if (Params.availableTypefaces.includes(font.family)) {
let stretch = font.stretch.replaceAll(`%`, ``);
let stretchValues = stretch.split(" ").map((x) => parseInt(x));
let stretchValues = [100, 100];
if (stretch !== `normal`) {
stretchValues = stretch.split(" ").map((x) => parseInt(x));
}

if (fonts.stretch.min > stretchValues[0]) {
fonts.stretch.min = stretchValues[0]
}
Expand Down
9 changes: 4 additions & 5 deletions src/client/controllers/Population.js
Original file line number Diff line number Diff line change
Expand Up @@ -254,8 +254,7 @@ export class Population {
tb["typeface"] = this.params["typography"]["typefaces"][r]["family"];
}

// size. double probability
if (Math.random() < prob*2) {
if (Math.random() < prob) {
let size = Math.round(tb["size"] + -SIZE_MUTATION_ADJUST+(Math.random()*SIZE_MUTATION_ADJUST));
// check if inside typeface min and max thresholds
size = Math.min(Math.max(size, ind.minFontSize), ind.maxFontSize);
Expand All @@ -271,7 +270,7 @@ export class Population {
tb["weight"] = Math.round(Math.random() * (maxWeight - minWeight) + minWeight);
}

// strech
// stretch
if (Math.random() < prob) {
let availableStretch = this.params["typography"]["typefaces"][selectedTypeface]["stretch"];
const minStretch = Math.max(parseInt(availableStretch[0]), this.params["typography"]["stretch"]["min"]);
Expand Down Expand Up @@ -317,7 +316,7 @@ export class Population {

// sort the population based on staticPenalty
// enables visualisation and elite
// sort individuals in the population by fitness (fittest first)
// sort individuals in the population by fitness (the fittest first)
await this.#staticPenalty();
}

Expand Down Expand Up @@ -434,7 +433,7 @@ export class Population {
// ensure that phenotype is created
if (ind.phenotype === null) {
this.updated = true;
await ind.evaluate(this.targetLayout);
await ind.evaluate(this.targetSemanticLayout);
}

// display
Expand Down
15 changes: 8 additions & 7 deletions src/client/controllers/Poster.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ class Poster {
this.generation = generation;
this.ready = false;
// ensure we use a deep copy of params
params = JSON.parse(JSON.stringify(params));
this.params = JSON.parse(JSON.stringify(params));

this.fitness = 1;
this.constraint = 0;
Expand All @@ -32,7 +32,6 @@ class Poster {

this.genotype = (genotype === null) ? this.#generateGenotype(params) : genotype;

// TODO: grid -> Local sotrage
this.#showGrid = params !== null ? params.display.grid : false;
this.phenotype = null;
// this.evaluate();
Expand Down Expand Up @@ -75,7 +74,7 @@ class Poster {
typography: typography,
images: images
}
return new Poster(this.n, this.generation, null, genotypeCopy);
return new Poster(this.n, this.generation, this.params, genotypeCopy);
}

#generateGenotype = (params) => {
Expand Down Expand Up @@ -225,14 +224,16 @@ class Poster {
}

evaluate = async (dist) => {
// TODO: DIVIDE into parts
this.phenotype = await this.draw();
const layoutSemantics = evaluator.layoutSemantics(this.genotype["grid"]["rows"]["l"], dist, `RELATIVE`, this.genotype["size"]);
const visualSemantics = evaluator.visualSemantics(this.genotype["textboxes"], dist);
const noCurrentTypefaces = this.params["typography"]["typefaces"].length;

// console.log (`visualSemantics`, visualSemantics);
const layoutSemantics = evaluator.layoutSemantics(this.genotype["grid"]["rows"]["l"], dist, `FIXED`, this.genotype["size"]);
const visualSemantics = evaluator.visualSemantics(this.genotype["textboxes"], dist, noCurrentTypefaces);
const justification = evaluator.legibility(this.sentencesLenght, this.genotype["grid"].getAvailableWidth(), `JUSTIFY`);

// this.fitness = layoutSemantics;
this.fitness = visualSemantics;
this.fitness = (visualSemantics * 0.3 + layoutSemantics * 0.3 + justification * 0.4);

// constraints
const legibility = evaluator.legibility(this.sentencesLenght, this.genotype["grid"].getAvailableWidth(), `OVERSET`);
Expand Down
6 changes: 3 additions & 3 deletions src/public/app.js

Large diffs are not rendered by default.

0 comments on commit c91fdf4

Please sign in to comment.