Merge branch 'charlesmchen/cropAndScaleAvatar'

pull/1/head
Matthew Chen 7 years ago
commit c08d81e45f

@ -33,6 +33,7 @@
3453D8EA1EC0D4ED003F9E6F /* OWSAlerts.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3453D8E91EC0D4ED003F9E6F /* OWSAlerts.swift */; };
345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671001E89A5F1006EE662 /* ThreadUtil.m */; };
3456710A1E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m in Sources */ = {isa = PBXBuildFile; fileRef = 345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */; };
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */; };
3471B1DA1EB7C63600F6AEC8 /* NewNonContactConversationViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */; };
3472229F1EB22FFE00E53955 /* AddToGroupViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 3472229E1EB22FFE00E53955 /* AddToGroupViewController.m */; };
348F2EAE1F0D21BC00D4ECE0 /* DeviceSleepManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 348F2EAD1F0D21BC00D4ECE0 /* DeviceSleepManager.swift */; };
@ -439,6 +440,7 @@
345671001E89A5F1006EE662 /* ThreadUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ThreadUtil.m; sourceTree = "<group>"; };
345671081E8A9F5D006EE662 /* TSGenericAttachmentAdapter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TSGenericAttachmentAdapter.h; sourceTree = "<group>"; };
345671091E8A9F5D006EE662 /* TSGenericAttachmentAdapter.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TSGenericAttachmentAdapter.m; sourceTree = "<group>"; };
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CropScaleImageViewController.swift; sourceTree = "<group>"; };
3471B1D81EB7C63600F6AEC8 /* NewNonContactConversationViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = NewNonContactConversationViewController.h; sourceTree = "<group>"; };
3471B1D91EB7C63600F6AEC8 /* NewNonContactConversationViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = NewNonContactConversationViewController.m; sourceTree = "<group>"; };
3472229D1EB22FFE00E53955 /* AddToGroupViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AddToGroupViewController.h; sourceTree = "<group>"; };
@ -1000,6 +1002,7 @@
3448BFC01EDF0EA7005B2D69 /* ConversationView */,
34B3F8401E8DF1700035BE1A /* CountryCodeViewController.h */,
34B3F8411E8DF1700035BE1A /* CountryCodeViewController.m */,
346B66301F4E29B200E5122F /* CropScaleImageViewController.swift */,
34D8C0221ED3673300188D7C /* DebugUI */,
3497DBED1ECE2E4700DB2605 /* DomainFrontingCountryViewController.h */,
3497DBEE1ECE2E4700DB2605 /* DomainFrontingCountryViewController.m */,
@ -2372,6 +2375,7 @@
34C42D661F4734ED0072EC04 /* OWSContactOffersInteraction.m in Sources */,
34B3F8941E8DF1710035BE1A /* SignalsViewController.m in Sources */,
34E8BF381EE9E2FD00F5F4CA /* FingerprintViewScanController.m in Sources */,
346B66311F4E29B200E5122F /* CropScaleImageViewController.swift in Sources */,
76EB058818170B33006006FC /* PropertyListPreferences.m in Sources */,
34330A611E788EA900DF2FB9 /* AttachmentUploadView.m in Sources */,
34B3F87D1E8DF1700035BE1A /* FullImageViewController.m in Sources */,
@ -2564,7 +2568,11 @@
"DEBUG=1",
"$(inherited)",
);
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = "DEBUG=1 $(inherited) SSK_BUILDING_FOR_TESTS=1";
"GCC_PREPROCESSOR_DEFINITIONS[arch=*]" = (
"DEBUG=1",
"$(inherited)",
"SSK_BUILDING_FOR_TESTS=1",
);
GCC_TREAT_IMPLICIT_FUNCTION_DECLARATIONS_AS_ERRORS = YES;
GCC_TREAT_INCOMPATIBLE_POINTER_TYPE_WARNINGS_AS_ERRORS = YES;
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;

@ -31,6 +31,7 @@
#import "TSMessageAdapter.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIImage+normalizeImage.h"
#import "UIUtil.h"
#import "UIView+OWS.h"
#import "ViewControllerUtils.h"

@ -4,6 +4,7 @@
#import "AvatarViewHelper.h"
#import "OWSContactsManager.h"
#import "OWSNavigationController.h"
#import "Signal-Swift.h"
#import "UIUtil.h"
#import <MobileCoreServices/UTCoreTypes.h>
@ -120,15 +121,25 @@ NS_ASSUME_NONNULL_BEGIN
UIImage *rawAvatar = [info objectForKey:UIImagePickerControllerOriginalImage];
if (rawAvatar) {
// We resize the avatar to fill a 210x210 square.
//
// See: GroupCreateActivity.java in Signal-Android.java.
UIImage *resizedAvatar = [rawAvatar resizedImageToFillPixelSize:CGSizeMake(210, 210)];
[self.delegate avatarDidChange:resizedAvatar];
}
[self.delegate.fromViewController dismissViewControllerAnimated:YES completion:nil];
[self.delegate.fromViewController
dismissViewControllerAnimated:YES
completion:^{
if (rawAvatar) {
OWSAssert([NSThread isMainThread]);
CropScaleImageViewController *vc = [[CropScaleImageViewController alloc]
initWithSrcImage:rawAvatar
successCompletion:^(UIImage *_Nonnull dstImage) {
[self.delegate avatarDidChange:dstImage];
}];
OWSNavigationController *navigationController =
[[OWSNavigationController alloc] initWithRootViewController:vc];
[self.delegate.fromViewController
presentViewController:navigationController
animated:YES
completion:[UIUtil modalCompletionBlock]];
}
}];
}
@end

@ -0,0 +1,510 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
import MediaPlayer
class OWSLayerView: UIView {
let layoutCallback : (() -> Void)
required init(frame: CGRect, layoutCallback : @escaping () -> Void) {
self.layoutCallback = layoutCallback
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
self.layoutCallback = {
}
super.init(coder: aDecoder)
}
override var bounds: CGRect {
didSet {
layoutCallback()
}
}
override var frame: CGRect {
didSet {
layoutCallback()
}
}
}
// This kind of view is tricky. I've tried to organize things in the
// simplest possible way.
//
// I've tried to avoid the following sources of confusion:
//
// * Points vs. pixels. All variables should have names that
// reflect the units. Pretty much everything is done in points
// except rendering of the output image which is done in pixels.
// * Coordinate systems. You have a) the src image coordinates
// b) the image view coordinates c) the output image coordinates.
// Wherever possible, I've tried to use src image coordinates.
// * Translation & scaling vs. crop region. The crop region is
// implicit. We represent the crop state using the translation
// and scaling of the "default" crop region (the largest possible
// crop region, at the origin (upper left) of the source image.
// Given the translation & scaling, we can determine a) the crop
// region b) the rectangle at which the src image should be rendered
// given a dst view or output context that will yield the
// appropriate cropping.
class CropScaleImageViewController: OWSViewController {
let TAG = "[CropScaleImageViewController]"
// MARK: Properties
let srcImage: UIImage
let successCompletion: ((UIImage) -> Void)
var imageView: UIView!
// We use a CALayer to render the image for performance reasons.
var imageLayer: CALayer!
var dashedBorderLayer: CAShapeLayer!
// In width/height.
//
// TODO: We could make this a parameter.
var dstSizePixels: CGSize {
return CGSize(width:210, height:210)
}
var dstAspectRatio: CGFloat {
return dstSizePixels.width / dstSizePixels.height
}
// The size of the src image in points.
var srcImageSizePoints: CGSize = CGSize.zero
// The size of the default crop region, which is the
// largest crop region with the correct dst aspect ratio
// that fits in the src image's aspect ratio,
// in src image point coordinates.
var srcDefaultCropSizePoints: CGSize = CGSize.zero
// N = Scaled, zoomed in.
let kMaxImageScale: CGFloat = 4.0
// 1.0 = Unscaled, cropped to fill crop rect.
let kMinImageScale: CGFloat = 1.0
// This represents the current scaling of the src image.
var imageScale: CGFloat = 1.0
// This represents the current translation from the
// upper-left corner of the src image to the upper-left
// corner of the crop region in src image point coordinates.
var srcTranslation: CGPoint = CGPoint.zero
// MARK: Initializers
@available(*, unavailable, message:"use srcImage:successCompletion: constructor instead.")
required init?(coder aDecoder: NSCoder) {
self.srcImage = UIImage(named:"fail")!
self.successCompletion = { _ in
}
super.init(coder: aDecoder)
owsFail("\(self.TAG) invalid constructor")
configureCropAndScale()
}
required init(srcImage: UIImage, successCompletion : @escaping (UIImage) -> Void) {
// normalized() can be slightly expensive but in practice this is fine.
self.srcImage = srcImage.normalized()
self.successCompletion = successCompletion
super.init(nibName: nil, bundle: nil)
configureCropAndScale()
}
// MARK: Cropping and Scaling
private func configureCropAndScale() {
// We use a "unit" view size (long dimension of length 1, short dimension reflects
// the dst aspect ratio) since we want to be able to perform this logic before we
// know the actual size of the cropped image view.
let unitSquareHeight: CGFloat = (dstAspectRatio >= 1.0 ? 1.0 : 1.0 / dstAspectRatio)
let unitSquareWidth: CGFloat = (dstAspectRatio >= 1.0 ? dstAspectRatio * unitSquareHeight : 1.0)
let unitSquareSize = CGSize(width: unitSquareWidth, height: unitSquareHeight)
srcImageSizePoints = srcImage.size
guard
(srcImageSizePoints.width > 0 && srcImageSizePoints.height > 0) else {
return
}
// Default
// The "default" (no scaling, no translation) crop frame, expressed in
// srcImage's coordinate system.
srcDefaultCropSizePoints = defaultCropSizePoints(dstSizePoints:unitSquareSize)
assert(srcImageSizePoints.width >= srcDefaultCropSizePoints.width)
assert(srcImageSizePoints.height >= srcDefaultCropSizePoints.height)
// By default, center the crop region in the src image.
srcTranslation = CGPoint(x:(srcImageSizePoints.width - srcDefaultCropSizePoints.width) * 0.5,
y:(srcImageSizePoints.height - srcDefaultCropSizePoints.height) * 0.5)
}
// Given a dst size, find the size of the largest crop region
// that fits in the src image.
private func defaultCropSizePoints(dstSizePoints: CGSize) -> (CGSize) {
assert(srcImageSizePoints.width > 0)
assert(srcImageSizePoints.height > 0)
let imageAspectRatio = srcImageSizePoints.width / srcImageSizePoints.height
let dstAspectRatio = dstSizePoints.width / dstSizePoints.height
var dstCropSizePoints = CGSize.zero
if imageAspectRatio > dstAspectRatio {
dstCropSizePoints = CGSize(width: dstSizePoints.width / dstSizePoints.height * srcImageSizePoints.height, height: srcImageSizePoints.height)
} else {
dstCropSizePoints = CGSize(width: srcImageSizePoints.width, height: dstSizePoints.height / dstSizePoints.width * srcImageSizePoints.width)
}
return dstCropSizePoints
}
// MARK: View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.white
self.navigationItem.leftBarButtonItem = UIBarButtonItem(barButtonSystemItem:.stop,
target:self,
action:#selector(cancelPressed))
self.navigationItem.title = NSLocalizedString("CROP_SCALE_IMAGE_VIEW_TITLE",
comment: "Title for the 'crop/scale image' dialog.")
createViews()
}
// MARK: - Create Views
private func createViews() {
let previewTopMargin: CGFloat = 30
let previewHMargin: CGFloat = 20
let contentView = UIView()
self.view.addSubview(contentView)
contentView.autoPinWidthToSuperview(withMargin:previewHMargin)
contentView.autoPin(toTopLayoutGuideOf: self, withInset:previewTopMargin)
createButtonRow(contentView:contentView)
let imageHMargin: CGFloat = 0
let imageView = OWSLayerView(frame:CGRect.zero, layoutCallback: {[weak self] _ in
guard let strongSelf = self else { return }
strongSelf.updateImageLayout()
})
imageView.clipsToBounds = true
self.imageView = imageView
contentView.addSubview(imageView)
imageView.autoPinWidthToSuperview(withMargin:imageHMargin)
imageView.autoVCenterInSuperview()
imageView.autoPinToSquareAspectRatio()
let imageLayer = CALayer()
self.imageLayer = imageLayer
imageLayer.contents = srcImage.cgImage
imageView.layer.addSublayer(imageLayer)
let dashedBorderLayer = CAShapeLayer()
self.dashedBorderLayer = dashedBorderLayer
dashedBorderLayer.strokeColor = UIColor.ows_materialBlue().cgColor
dashedBorderLayer.lineDashPattern = [10, 10]
dashedBorderLayer.lineWidth = 4
dashedBorderLayer.fillColor = nil
imageView.layer.addSublayer(dashedBorderLayer)
contentView.isUserInteractionEnabled = true
contentView.addGestureRecognizer(UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(sender:))))
contentView.addGestureRecognizer(UIPanGestureRecognizer(target: self, action: #selector(handlePan(sender:))))
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
updateImageLayout()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
self.view.layoutSubviews()
updateImageLayout()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
updateImageLayout()
}
// Given a src image size and a dst view size, this finds the bounds
// of the largest rectangular crop region with the correct dst aspect
// ratio that fits in the src image's aspect ratio, in src image point
// coordinates.
private func defaultCropFramePoints(imageSizePoints: CGSize, viewSizePoints: CGSize) -> (CGRect) {
let imageAspectRatio = imageSizePoints.width / imageSizePoints.height
let viewAspectRatio = viewSizePoints.width / viewSizePoints.height
var defaultCropSizePoints = CGSize.zero
if imageAspectRatio > viewAspectRatio {
defaultCropSizePoints = CGSize(width: viewSizePoints.width / viewSizePoints.height * imageSizePoints.height, height: imageSizePoints.height)
} else {
defaultCropSizePoints = CGSize(width: imageSizePoints.width, height: viewSizePoints.height / viewSizePoints.width * imageSizePoints.width)
}
let defaultCropOriginPoints = CGPoint(x: (imageSizePoints.width - defaultCropSizePoints.width) * 0.5,
y: (imageSizePoints.height - defaultCropSizePoints.height) * 0.5)
assert(defaultCropOriginPoints.x >= 0)
assert(defaultCropOriginPoints.y >= 0)
assert(defaultCropOriginPoints.x <= imageSizePoints.width - defaultCropSizePoints.width)
assert(defaultCropOriginPoints.y <= imageSizePoints.height - defaultCropSizePoints.height)
return CGRect(origin:defaultCropOriginPoints, size:defaultCropSizePoints)
}
// Updates the image view _AND_ normalizes the current scale/translate state.
private func updateImageLayout() {
guard let imageView = self.imageView else {
return
}
guard srcImageSizePoints.width > 0 && srcImageSizePoints.height > 0 else {
return
}
guard srcDefaultCropSizePoints.width > 0 && srcDefaultCropSizePoints.height > 0 else {
return
}
let viewSizePoints = imageView.frame.size
guard
(viewSizePoints.width > 0 && viewSizePoints.height > 0) else {
return
}
// Normalize the scaling property.
imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale))
let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
let minSrcTranslationPoints = CGPoint.zero
let maxSrcTranslationPoints = CGPoint(x:srcImageSizePoints.width - srcCropSizePoints.width,
y:srcImageSizePoints.height - srcCropSizePoints.height
)
// Normalize the translation property.
srcTranslation = CGPoint(x: max(minSrcTranslationPoints.x, min(maxSrcTranslationPoints.x, srcTranslation.x)),
y: max(minSrcTranslationPoints.y, min(maxSrcTranslationPoints.y, srcTranslation.y)))
let imageViewFrame = imageRenderRect(forDstSize:viewSizePoints)
// Disable implicit animations.
CATransaction.begin()
CATransaction.setDisableActions(true)
imageLayer.frame = imageViewFrame
// Mask to circle.
let maskLayer = CAShapeLayer()
maskLayer.frame = imageViewFrame
maskLayer.fillRule = kCAFillRuleEvenOdd
let maskFrame = CGRect(origin:CGPoint(x:-imageViewFrame.origin.x * 2,
y: -imageViewFrame.origin.y * 2),
size:imageView.bounds.size)
maskLayer.path =
CGPath(ellipseIn: maskFrame, transform: nil)
imageLayer.mask = maskLayer
dashedBorderLayer.frame = imageView.bounds
dashedBorderLayer.path = UIBezierPath(rect: imageView.bounds).cgPath
CATransaction.commit()
}
private func imageRenderRect(forDstSize dstSize: CGSize) -> CGRect {
let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
let srcToViewRatio = dstSize.width / srcCropSizePoints.width
return CGRect(origin: CGPoint(x:srcTranslation.x * -srcToViewRatio,
y:srcTranslation.y * -srcToViewRatio),
size: CGSize(width:srcImageSizePoints.width * +srcToViewRatio,
height:srcImageSizePoints.height * +srcToViewRatio
))
}
var srcTranslationAtPinchStart: CGPoint = CGPoint.zero
var imageScaleAtPinchStart: CGFloat = 0
var lastPinchLocation: CGPoint = CGPoint.zero
var lastPinchScale: CGFloat = 1.0
func handlePinch(sender: UIPinchGestureRecognizer) {
switch (sender.state) {
case .possible:
break
case .began:
srcTranslationAtPinchStart = srcTranslation
imageScaleAtPinchStart = imageScale
lastPinchLocation =
sender.location(in: sender.view)
lastPinchScale = sender.scale
break
case .changed, .ended:
if sender.numberOfTouches > 1 {
let location =
sender.location(in: sender.view)
let scaleDiff = sender.scale / lastPinchScale
// Update scaling.
let srcCropSizeBeforeScalePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
imageScale = max(kMinImageScale, min(kMaxImageScale, imageScale * scaleDiff))
let srcCropSizeAfterScalePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
// Since the translation state reflects the "upper left" corner of the crop region, we need to
// adjust the translation when scaling to preserve the "center" of the crop region.
srcTranslation.x += (srcCropSizeBeforeScalePoints.width - srcCropSizeAfterScalePoints.width) * 0.5
srcTranslation.y += (srcCropSizeBeforeScalePoints.height - srcCropSizeAfterScalePoints.height) * 0.5
// Update translation.
let viewSizePoints = imageView.frame.size
let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
let viewToSrcRatio = srcCropSizePoints.width / viewSizePoints.width
let gestureTranslation = CGPoint(x:location.x - lastPinchLocation.x,
y:location.y - lastPinchLocation.y)
srcTranslation = CGPoint(x:srcTranslation.x + gestureTranslation.x * -viewToSrcRatio,
y:srcTranslation.y + gestureTranslation.y * -viewToSrcRatio)
lastPinchLocation = location
lastPinchScale = sender.scale
}
break
case .cancelled, .failed:
srcTranslation = srcTranslationAtPinchStart
imageScale = imageScaleAtPinchStart
break
}
updateImageLayout()
}
var srcTranslationAtPanStart: CGPoint = CGPoint.zero
func handlePan(sender: UIPanGestureRecognizer) {
switch (sender.state) {
case .possible:
break
case .began:
srcTranslationAtPanStart = srcTranslation
break
case .changed, .ended:
let viewSizePoints = imageView.frame.size
let srcCropSizePoints = CGSize(width:srcDefaultCropSizePoints.width / imageScale,
height:srcDefaultCropSizePoints.height / imageScale)
let viewToSrcRatio = srcCropSizePoints.width / viewSizePoints.width
let gestureTranslation =
sender.translation(in: sender.view)
// Update translation.
srcTranslation = CGPoint(x:srcTranslationAtPanStart.x + gestureTranslation.x * -viewToSrcRatio,
y:srcTranslationAtPanStart.y + gestureTranslation.y * -viewToSrcRatio)
break
case .cancelled, .failed:
srcTranslation
= srcTranslationAtPanStart
break
}
updateImageLayout()
}
private func createButtonRow(contentView: UIView) {
let buttonTopMargin = ScaleFromIPhone5To7Plus(30, 40)
let buttonBottomMargin = ScaleFromIPhone5To7Plus(25, 40)
let buttonRow = UIView()
self.view.addSubview(buttonRow)
buttonRow.autoPinWidthToSuperview()
buttonRow.autoPinEdge(toSuperviewEdge:.bottom, withInset:buttonBottomMargin)
buttonRow.autoPinEdge(.top, to:.bottom, of:contentView, withOffset:buttonTopMargin)
let doneButton = createButton(title: NSLocalizedString("BUTTON_DONE",
comment: "Label for generic done button."),
color : UIColor.ows_materialBlue(),
action: #selector(donePressed))
buttonRow.addSubview(doneButton)
doneButton.autoPinEdge(toSuperviewEdge:.top)
doneButton.autoPinEdge(toSuperviewEdge:.bottom)
doneButton.autoHCenterInSuperview()
}
private func createButton(title: String, color: UIColor, action: Selector) -> UIButton {
let buttonFont = UIFont.ows_mediumFont(withSize:ScaleFromIPhone5To7Plus(18, 22))
let buttonCornerRadius = ScaleFromIPhone5To7Plus(4, 5)
let buttonWidth = ScaleFromIPhone5To7Plus(110, 140)
let buttonHeight = ScaleFromIPhone5To7Plus(35, 45)
let button = UIButton()
button.setTitle(title, for:.normal)
button.setTitleColor(UIColor.white, for:.normal)
button.titleLabel!.font = buttonFont
button.backgroundColor = color
button.layer.cornerRadius = buttonCornerRadius
button.clipsToBounds = true
button.addTarget(self, action:action, for:.touchUpInside)
button.autoSetDimension(.width, toSize:buttonWidth)
button.autoSetDimension(.height, toSize:buttonHeight)
return button
}
// MARK: - Event Handlers
func cancelPressed(sender: UIButton) {
dismiss(animated: true, completion:nil)
}
func donePressed(sender: UIButton) {
let successCompletion = self.successCompletion
dismiss(animated: true, completion: {
guard let dstImage = self.generateDstImage() else {
return
}
successCompletion(dstImage)
})
}
// MARK: - Output
func generateDstImage() -> UIImage? {
let hasAlpha = false
let dstScale: CGFloat = 1.0 // The size is specified in pixels, not in points.
UIGraphicsBeginImageContextWithOptions(dstSizePixels, !hasAlpha, dstScale)
let context = UIGraphicsGetCurrentContext()
context!.interpolationQuality = .high
let imageViewFrame = imageRenderRect(forDstSize:dstSizePixels)
srcImage.draw(in:imageViewFrame)
let scaledImage = UIGraphicsGetImageFromCurrentImageContext()
if scaledImage == nil {
owsFail("\(TAG) could not generate dst image.")
}
UIGraphicsEndImageContext()
return scaledImage
}
}

@ -205,6 +205,9 @@
/* Title format for action sheet that offers to block an unknown user.Embeds {{the unknown user's name or phone number}}. */
"BLOCK_OFFER_ACTIONSHEET_TITLE_FORMAT" = "Block %@?";
/* Label for generic done button. */
"BUTTON_DONE" = "Done";
/* Alert message when calling and permissions for microphone are missing */
"CALL_AUDIO_PERMISSION_MESSAGE" = "Signal requires access to your microphone to make calls and record voice messages. You can grant this permission in the Settings app.";
@ -385,6 +388,9 @@
/* Accessibility label for the create group new group button */
"CREATE_NEW_GROUP" = "Create new group";
/* Title for the 'crop/scale image' dialog. */
"CROP_SCALE_IMAGE_VIEW_TITLE" = "Crop Image";
/* Subtitle shown while the app is updating its database. */
"DATABASE_VIEW_OVERLAY_SUBTITLE" = "This can take a few minutes.";

Loading…
Cancel
Save