se.trixon.pacoma.collage.Page.java Source code

Java tutorial

Introduction

Here is the source code for se.trixon.pacoma.collage.Page.java

Source

/*
 * Copyright 2017 Patrik Karlsson.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package se.trixon.pacoma.collage;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.Random;
import org.apache.commons.lang3.StringUtils;

/**
 * Based on work by Adrien Verg in https://github.com/adrienverge/PhotoCollage
 *
 * @author Patrik Karlsson
 */
public class Page {

    private final LinkedList<Column> mColumns = new LinkedList<>();
    /*
    Properties:
    <-------- w -------->
    ---------------------- ^
    |                    | |
    |                    |
    |        Page        | h
    |                    |
    |                    | |
    ---------------------- v
     */
    private final double mTargetRatio;

    Page(int w, double targetRatio, int numOfCols) {
        mTargetRatio = targetRatio;

        int colW = (int) Math.round(1.0 * w / numOfCols);
        for (int i = 0; i < numOfCols; i++) {
            mColumns.add(new Column(this, colW));
        }
    }

    /**
     *
     * @return Representation of the page in ASCII art
     */
    @Override
    public String toString() {
        /*
        Returns something like:
        [62 52]    [125 134-- ------]    [62 87]
        [62 47]    [62 66]    [125 132-- [62 45]
        [62 46]    ------]    [62 49]    ------]
        [62 78]    ------]    [62 49]    [62 45]
        [125 102-- ------]    [62 49]    [62 65]
        [125 135--            [62 85]    [62 53]
        [125 91--             [125 89--  [62 64]
                             ------]
         */
        LinkedList<String> lines = new LinkedList<>();
        int n = 0;
        boolean end = false;
        while (!end) {
            lines.add("");
            end = true;
            for (Column col : mColumns) {
                String[] cells = col.toString().split("\\n");
                int w = 0;
                for (String s : cells) {
                    w = Math.max(w, s.length());
                }
                if (col != mColumns.getLast()) {
                    w += 1;
                }
                String cell = StringUtils.repeat(" ", w);
                if (n < cells.length) {
                    cell = cells[n] + StringUtils.repeat(" ", w - cells[n].length());
                    if (n < cells.length - 1) {
                        end = false;
                    }
                }
                lines.set(lines.size() - 1, lines.getLast() + cell);
            }
            n++;
        }

        return String.join("\n", lines);
    }

    private void addCellMultiColumn(Column column1, Column column2, Photo photo) {
        LinkedList<Column> columnList = new LinkedList<>();
        columnList.add(column1);
        columnList.add(column2);
        Cell cell = new Cell(photo, columnList);
        Extent cellExtent = new Extent(cell);
        column1.getCells().add(cell);
        column2.getCells().add(cellExtent);
    }

    private void addCellSingleColumn(Column column, Photo photo) {
        LinkedList<Column> columnList = new LinkedList<>();
        columnList.add(column);
        column.getCells().add(new Cell(photo, columnList));
    }

    /**
     * Set all columns' heights to same value by shrinking them
     */
    private void adjustColumnHeights() {
        double targetHeight = getWidth() * mTargetRatio;
        mColumns.forEach((column) -> {
            column.adjustHeight((int) targetHeight);
        });
    }

    private Cell getCellAtPosition(int x, int y) {
        for (Column col : mColumns) {
            if (x >= col.getX() && x < col.getX() + col.getWidth()) {
                for (Cell cell : col.getCells()) {
                    if (y >= cell.getY() && y < cell.getY() + cell.getHeight()) {
                        if (cell.isExtension()) {
                            return ((Extent) cell).getOrigin();
                        } else {
                            return cell;
                        }
                    }
                }
            }
        }

        return null;
    }

    private int getHeight() {
        return mColumns.stream().mapToInt(Column::getHeight).max().getAsInt();
    }

    /**
     *
     * @return the column with lowest height
     */
    private Column getNextFreeColumn() {
        int minHeight = mColumns.stream().mapToInt(Column::getHeight).min().getAsInt();
        ArrayList<Column> candidates = new ArrayList<>();

        mColumns.stream().filter((column) -> (column.getHeight() == minHeight)).forEachOrdered((column) -> {
            candidates.add(column);
        });

        return candidates.get(new Random().nextInt(candidates.size()));
    }

    private int getNumOfCols() {
        return mColumns.size();
    }

    private double getRatio() {
        return 1.0 * getHeight() / getWidth();
    }

    private double getWidth() {
        return mColumns.stream().mapToDouble(Column::getWidth).sum();
    }

    /**
     * Remove holes created by extended cells
     */
    private void removeBottomHoles() {
        /*
        Example (case A):
        The bottom-right cell should be extended to fill the hole.
        ----------------------             ----------------------
        |      |      |      |             |      |      |      |
        |      |-------------|             |      |-------------|
        |------|             |             |------|             |
        |      |--------------             |      |--------------
        |      |      |  ^                 |      |  ^   |      |
        --------------- hole               -------- hole --------
            
        Example (case B):
        The bottom cell should be moved under the other extended cell.
        ----------------------             ----------------------
        |      |      |      |             |      |      |      |
        |------|-------------|             |-------------|------|
        |      |             |             |             |      |
        |---------------------             ---------------------|
        |             |   <-- hole      hole ->   |             |
        ---------------                           ---------------
         */
        for (Column col : mColumns) {
            Cell cell = col.getCells().getLast();
            if (cell == col.getCells().getFirst()) {
                continue;
            }

            // Case A
            // If cell is not extended, is below an extended cell and has no
            // neighbour under the latter, it should be extended.
            if (!cell.isExtended() && !cell.isExtension()) {
                //Case A1
                if (cell.getTopNeighbor().isExtended()
                        && cell.getTopNeighbor().getExtent().getBottomNeighbor() == null) {
                    //Extend cell to right
                    Extent extent = new Extent(cell);
                    col.getRightNeighbor().getCells().add(extent);
                    LinkedList<Column> parents = new LinkedList<>();
                    parents.add(col);
                    parents.add(col.getRightNeighbor());
                    cell.setParents(parents);
                    //Case A2
                } else if (cell.getTopNeighbor().isExtension()
                        && cell.getTopNeighbor().getOrigin().getBottomNeighbor() == null) {
                    //Extend cell to left
                    col.getCells().remove(cell);
                    col.getLeftNeighbor().getCells().add(cell);
                    Extent extent = new Extent(cell);
                    col.getCells().add(extent);
                    LinkedList<Column> parents = new LinkedList<>();
                    parents.add(col.getLeftNeighbor());
                    parents.add(col);
                    cell.setParents(parents);
                }

                //Case B
                //If cell is extended and one of the cells above is extended too,
                //the bottom cell should be placed right below the top one.
            } else if (cell.isExtended() && cell.getExtent().getBottomNeighbor() == null) {
                //Case B1
                if (cell.getExtent().getTopNeighbor().isExtended()
                        && cell.getExtent().getTopNeighbor().getExtent().getBottomNeighbor() == null) {
                    //Move cell to right
                    col.getCells().remove(cell);
                    col.getRightNeighbor().getCells().remove(cell.getExtent());
                    col.getRightNeighbor().getCells().add(cell);
                    col.getRightNeighbor().getRightNeighbor().getCells().add(cell.getExtent());
                    LinkedList<Column> parents = new LinkedList<>();
                    parents.add(col.getRightNeighbor());
                    parents.add(col.getRightNeighbor().getRightNeighbor());
                    cell.setParents(parents);
                    //Case B2
                } else if (cell.getTopNeighbor().isExtension()
                        && cell.getTopNeighbor().getOrigin().getBottomNeighbor() == null) {
                    //Move cell to left
                    col.getCells().remove(cell);
                    col.getRightNeighbor().getCells().remove(cell.getExtent());
                    col.getLeftNeighbor().getCells().add(cell);
                    col.getCells().add(cell.getExtent());
                    LinkedList<Column> parents = new LinkedList<>();
                    parents.add(col.getLeftNeighbor());
                    parents.add(col);
                    cell.setParents(parents);
                }
            }
        }
    }

    private void removeEmptyCols() {
        for (Iterator<Column> iterator = mColumns.iterator(); iterator.hasNext();) {
            Column column = iterator.next();
            if (column.getCells().isEmpty()) {
                iterator.remove();
            }
        }
    }

    private void scale(double alpha) {
        mColumns.forEach((column) -> {
            column.scale(alpha);
        });
    }

    private void scaleToFit(double maxWidth, Double maxHeight) {
        if (maxHeight == null || getWidth() * maxHeight > getHeight() * maxWidth) {
            scale(maxWidth / getWidth());
        } else {
            scale(maxHeight / getHeight());
        }
    }

    private void swapPhotos(Cell cell1, Cell cell2) {
        Photo photo2 = cell2.getPhoto();
        cell2.setPhoto(cell1.getPhoto());
        cell1.setPhoto(photo2);
    }

    /**
     * Add a new cell in the best computed place. If possible, and if it's worth, make a
     * "multiple-column" cell.
     *
     * @param photo
     */
    void addCell(Photo photo) {
        Column col = getNextFreeColumn();
        Column left = col.getLeftNeighbor();
        Column right = col.getRightNeighbor();

        if (2 * new Random().nextDouble() > photo.getRatio()) {
            if (left != null && Math.abs(col.getHeight() - left.getHeight()) < 0.5 * col.getWidth()) {
                addCellMultiColumn(left, col, photo);
                return;
            } else if (right != null && Math.abs(col.getHeight() - right.getHeight()) < 0.5 * col.getWidth()) {
                addCellMultiColumn(col, right, photo);
                return;
            }
        }

        addCellSingleColumn(col, photo);
    }

    void adjust() {
        removeEmptyCols();
        removeBottomHoles();
        adjustColumnHeights();
    }

    LinkedList<Column> getColumns() {
        return mColumns;
    }

}
/*
class Page(object):
"""Represents a whole page
    
Properties:
<-------- w -------->
---------------------- ^
|                    | |
|                    |
|        Page        | h
|                    |
|                    | |
---------------------- v
    
"""
def __init__(self, w, target_ratio, no_cols):
    self.target_ratio = target_ratio
    col_w = float(w)/no_cols
    self.cols = []
    for i in range(no_cols):
        self.cols.append(Column(self, col_w))
    
def __repr__(self):
    """Representation of the page in ASCII art
    
    Returns something like:
    [62 52]    [125 134-- ------]    [62 87]
    [62 47]    [62 66]    [125 132-- [62 45]
    [62 46]    ------]    [62 49]    ------]
    [62 78]    ------]    [62 49]    [62 45]
    [125 102-- ------]    [62 49]    [62 65]
    [125 135--            [62 85]    [62 53]
    [125 91--             [125 89--  [62 64]
                             ------]
    """
    lines = []
    n = 0
    end = False
    while not end:
        lines.append("")
        end = True
        for col in self.cols:
            cells = col.__repr__().split("\n")
            w = max(len(cell) for cell in cells)
            if col != self.cols[-1]:
                w += 1
            cell = w * " "
            if n < len(cells):
                cell = cells[n] + (w - len(cells[n])) * " "
                if n < len(cells) - 1:
                    end = False
            lines[-1] += cell
        n += 1
    return "\n".join(lines)
    
@property
def no_cols(self):
    return len(self.cols)
    
@property
def w(self):
    return sum(c.w for c in self.cols)
    
@property
def h(self):
    return max(c.h for c in self.cols)
    
@property
def ratio(self):
    return self.h / self.w
    
def scale(self, alpha):
    for c in self.cols:
        c.scale(alpha)
    
def scale_to_fit(self, max_w, max_h=None):
    if max_h is None or self.w * max_h > self.h * max_w:
        self.scale(max_w / self.w)
    else:
        self.scale(max_h / self.h)
    
def next_free_col(self):
    """Returns the column with lowest height"""
    minimum = min(c.h for c in self.cols)
    candidates = []
    for c in self.cols:
        if c.h == minimum:
            candidates.append(c)
    return random.choice(candidates)
    
def add_cell_single_col(self, col, photo):
    col.cells.append(Cell((col,), photo))
    
def add_cell_multi_col(self, col1, col2, photo):
    cell = Cell((col1, col2), photo)
    extent = CellExtent(cell)
    col1.cells.append(cell)
    col2.cells.append(extent)
    
def add_cell(self, photo):
    """Add a new cell in the best computed place
    
    If possible, and if it's worth, make a "multiple-column" cell.
    
    """
    col = self.next_free_col()
    left = col.left_neighbor()
    right = col.right_neighbor()
    if 2 * random.random() > photo.ratio:
        if left and abs(col.h - left.h) < 0.5 * col.w:
            return self.add_cell_multi_col(left, col, photo)
        elif right and abs(col.h - right.h) < 0.5 * col.w:
            return self.add_cell_multi_col(col, right, photo)
    
    self.add_cell_single_col(col, photo)
    
def remove_empty_cols(self):
    i = 0
    while i < len(self.cols):
        if len(self.cols[i].cells) == 0:
            self.cols.pop(i)
        else:
            i += 1
    
def remove_bottom_holes(self):
    """Remove holes created by extended cells
    
    Example (case A):
    The bottom-right cell should be extended to fill the hole.
    ----------------------             ----------------------
    |      |      |      |             |      |      |      |
    |      |-------------|             |      |-------------|
    |------|             |             |------|             |
    |      |--------------             |      |--------------
    |      |      |  ^                 |      |  ^   |      |
    --------------- hole               -------- hole --------
    
    Example (case B):
    The bottom cell should be moved under the other extended cell.
    ----------------------             ----------------------
    |      |      |      |             |      |      |      |
    |------|-------------|             |-------------|------|
    |      |             |             |             |      |
    |---------------------             ---------------------|
    |             |   <-- hole      hole ->   |             |
    ---------------                           ---------------
    
    """
    for col in self.cols:
        cell = col.cells[-1]
        if cell == col.cells[0]:
            continue
    
        # Case A
        # If cell is not extended, is below an extended cell and has no
        # neighbour under the latter, it should be extended.
        if not cell.is_extended() and not cell.is_extension():
            # Case A1
            if cell.top_neighbor().is_extended() \
                    and cell.top_neighbor().extent \
                    .bottom_neighbor() is None:
                # Extend cell to right
                extent = CellExtent(cell)
                col.right_neighbor().cells.append(extent)
                cell.parents = (col, col.right_neighbor())
            # Case A2
            elif cell.top_neighbor().is_extension() \
                    and cell.top_neighbor().origin \
                    .bottom_neighbor() is None:
                # Extend cell to left
                col.cells.remove(cell)
                col.left_neighbor().cells.append(cell)
                extent = CellExtent(cell)
                col.cells.append(extent)
                cell.parents = (col.left_neighbor(), col)
        # Case B
        # If cell is extended and one of the cells above is extended too,
        # the bottom cell should be placed right below the top one.
        elif cell.is_extended() and cell.extent.bottom_neighbor() is None:
            # Case B1
            if cell.extent.top_neighbor().is_extended() \
                    and cell.extent.top_neighbor().extent \
                    .bottom_neighbor() is None:
                # Move cell to right
                col.cells.remove(cell)
                col.right_neighbor().cells.remove(cell.extent)
                col.right_neighbor().cells.append(cell)
                col.right_neighbor().right_neighbor().cells \
                    .append(cell.extent)
                cell.parents = (col.right_neighbor(),
                                col.right_neighbor().right_neighbor())
            # Case B2
            elif cell.top_neighbor().is_extension() \
                    and cell.top_neighbor().origin \
                    .bottom_neighbor() is None:
                # Move cell to left
                col.cells.remove(cell)
                col.right_neighbor().cells.remove(cell.extent)
                col.left_neighbor().cells.append(cell)
                col.cells.append(cell.extent)
                cell.parents = (col.left_neighbor(), col)
    
def adjust_cols_heights(self):
    """Set all columns' heights to same value by shrinking them"""
    target_h = self.w * self.target_ratio
    for c in self.cols:
        c.adjust_height(target_h)
    
def adjust(self):
    self.remove_empty_cols()
    self.remove_bottom_holes()
    self.adjust_cols_heights()
    
def get_cell_at_position(self, x, y):
    for col in self.cols:
        if x >= col.x and x < col.x + col.w:
            for cell in col.cells:
                if y >= cell.y and y < cell.y + cell.h:
                    if cell.is_extension():
                        return cell.origin
                    return cell
    return None
    
def swap_photos(self, cell1, cell2):
    cell1.photo, cell2.photo = cell2.photo, cell1.photo
 */