You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
157 lines
6.3 KiB
157 lines
6.3 KiB
/* Copyright 2018 Jochem Raat <jchmrt@riseup.net>
|
|
*
|
|
* This file is a part of Tusky.
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify it under the terms of the
|
|
* GNU General Public License as published by the Free Software Foundation; either version 3 of the
|
|
* License, or (at your option) any later version.
|
|
*
|
|
* Tusky is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
|
|
* the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
|
|
* Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License along with Tusky; if not,
|
|
* see <http://www.gnu.org/licenses>. */
|
|
|
|
package com.keylesspalace.tusky.util
|
|
|
|
import android.graphics.Matrix
|
|
|
|
import com.keylesspalace.tusky.entity.Attachment.Focus
|
|
|
|
/**
|
|
* Calculates the image matrix needed to maintain the correct cropping for image views based on
|
|
* their focal point.
|
|
*
|
|
* The purpose of this class is to make sure that the focal point information on media
|
|
* attachments are honoured. This class uses the custom matrix option of android ImageView's to
|
|
* customize how the image is cropped into the view.
|
|
*
|
|
* See the explanation of focal points here:
|
|
* https://github.com/jonom/jquery-focuspoint#1-calculate-your-images-focus-point
|
|
*/
|
|
object FocalPointUtil {
|
|
/**
|
|
* Update the given matrix for the given parameters.
|
|
*
|
|
* How it works is using the following steps:
|
|
* - First we determine if the image is too wide or too tall for the view size. If it is
|
|
* too wide, we need to crop it horizontally and scale the height to fit the view
|
|
* exactly. If it is too tall we need to crop vertically and scale the width to fit the
|
|
* view exactly.
|
|
* - Then we determine what translation is needed to get the focal point in view. We
|
|
* prefer to get the focal point at the center of the preview. However if that would
|
|
* result in some part of the preview being empty, we instead align the image so that it
|
|
* fills the view, but still the focal point is always in view.
|
|
*
|
|
* @param viewWidth The width of the imageView.
|
|
* @param viewHeight The height of the imageView
|
|
* @param imageWidth The width of the actual image
|
|
* @param imageHeight The height of the actual image
|
|
* @param focus The focal point to focus
|
|
* @param mat The matrix to update, this matrix is reset() and then updated with the new
|
|
* configuration. We reuse the old matrix to prevent unnecessary allocations.
|
|
*
|
|
* @return The matrix which correctly crops the image
|
|
*/
|
|
fun updateFocalPointMatrix(viewWidth: Float,
|
|
viewHeight: Float,
|
|
imageWidth: Float,
|
|
imageHeight: Float,
|
|
focus: Focus,
|
|
mat: Matrix) {
|
|
// Reset the cached matrix:
|
|
mat.reset()
|
|
|
|
// calculate scaling:
|
|
val scale = calculateScaling(viewWidth, viewHeight, imageWidth, imageHeight)
|
|
mat.preScale(scale, scale)
|
|
|
|
// calculate offsets:
|
|
var top = 0f
|
|
var left = 0f
|
|
if (isVerticalCrop(viewWidth, viewHeight, imageWidth, imageHeight)) {
|
|
top = focalOffset(viewHeight, imageHeight, scale, focalYToCoordinate(focus.y))
|
|
} else { // horizontal crop
|
|
left = focalOffset(viewWidth, imageWidth, scale, focalXToCoordinate(focus.x))
|
|
}
|
|
|
|
mat.postTranslate(left, top)
|
|
}
|
|
|
|
/**
|
|
* Calculate the scaling of the image needed to make it fill the screen.
|
|
*
|
|
* The scaling used depends on if we need a vertical of horizontal crop.
|
|
*/
|
|
fun calculateScaling(viewWidth: Float, viewHeight: Float,
|
|
imageWidth: Float, imageHeight: Float): Float {
|
|
return if (isVerticalCrop(viewWidth, viewHeight, imageWidth, imageHeight)) {
|
|
viewWidth / imageWidth
|
|
} else { // horizontal crop:
|
|
viewHeight / imageHeight
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Return true if we need a vertical crop, false for a horizontal crop.
|
|
*/
|
|
fun isVerticalCrop(viewWidth: Float, viewHeight: Float,
|
|
imageWidth: Float, imageHeight: Float): Boolean {
|
|
val viewRatio = viewWidth / viewHeight
|
|
val imageRatio = imageWidth / imageHeight
|
|
|
|
return viewRatio > imageRatio
|
|
}
|
|
|
|
/**
|
|
* Transform the focal x component to the corresponding coordinate on the image.
|
|
*
|
|
* This means that we go from a representation where the left side of the image is -1 and
|
|
* the right side +1, to a representation with the left side being 0 and the right side
|
|
* being +1.
|
|
*/
|
|
fun focalXToCoordinate(x: Float): Float {
|
|
return (x + 1) / 2
|
|
}
|
|
|
|
/**
|
|
* Transform the focal y component to the corresponding coordinate on the image.
|
|
*
|
|
* This means that we go from a representation where the bottom side of the image is -1 and
|
|
* the top side +1, to a representation with the top side being 0 and the bottom side
|
|
* being +1.
|
|
*/
|
|
fun focalYToCoordinate(y: Float): Float {
|
|
return (-y + 1) / 2
|
|
}
|
|
|
|
/**
|
|
* Calculate the relative offset needed to focus on the focal point in one direction.
|
|
*
|
|
* This method works for both the vertical and horizontal crops. It simply calculates
|
|
* what offset to take based on the proportions between the scaled image and the view
|
|
* available. It also makes sure to always fill the bounds of the view completely with
|
|
* the image. So it won't put the very edge of the image in center, because that would
|
|
* leave part of the view empty.
|
|
*/
|
|
fun focalOffset(view: Float, image: Float,
|
|
scale: Float, focal: Float): Float {
|
|
// The fraction of the image that will be in view:
|
|
val inView = view / (scale * image)
|
|
var offset = 0f
|
|
|
|
// These values indicate the maximum and minimum focal parameter possible while still
|
|
// keeping the entire view filled with the image:
|
|
val maxFocal = 1 - inView / 2
|
|
val minFocal = inView / 2
|
|
|
|
if (focal > maxFocal) {
|
|
offset = -((2 - inView) / 2) * image * scale + view * 0.5f
|
|
} else if (focal > minFocal) {
|
|
offset = -focal * image * scale + view * 0.5f
|
|
}
|
|
|
|
return offset
|
|
}
|
|
}
|
|
|