Upgrade to Pro — share decks privately, control downloads, hide ads and more …

iOS 앱 이미지 에디터 개발기

kakao
November 01, 2024

iOS 앱 이미지 에디터 개발기

#ios #image editor #2D computer graphics

브런치, 티스토리에 적용된 iOS 이미지 에디터 개발 과정을 소개하고, 그 과정에서 겪은 어려움을 공유합니다.
특히 회전, 반전, 자르기 등을 지원하는 상황에서 여러 편집이 적용된 이미지를 처리하는 방법을 중점적으로 다룹니다.

발표자 : woongs.rich
브런치, 티스토리 iOS 앱을 개발하고 있습니다.

kakao

November 01, 2024
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. "11

  2. struct Shader: Equatable { var vertex: Vertex var fragment: Fragment

    enum Vertex: String { case passthrough = "vertexPassthrough" } enum Fragment: String { case passthrough = "fragmentPassthrough" case lookup = "fragmentLookup" case pixellate = "pixellate" } }
  3. $PNNBOE CVGGFS $PNNBOE FODPEFS 7FSUFY 3BTUFSJ[F 'SBHNFOU 3FOEFS1JQFMJOF .5-3FOEFS$PNNBOE&ODPEFS .5-$PNNBOE#VGGFS

    .5-3FOEFS1JQFMJOF4UBUF FODPEFSESBX1SJNJUJWFT j NFUBM NFUBM class ImageFilter { init(shader: Shader, inputTextures: [MTLTexture] = []) { self.shader = shader self.inputTextures = inputTextures // with shader self.renderPipelineState = ... self.fragmentArguments = ... self.fragmentArgumentsInfo = ... self.fragmentBuffers = ... } } class ImageFilter {
  4. $PNNBOE CVGGFS $PNNBOE FODPEFS 7FSUFY 3BTUFSJ[F 'SBHNFOU 3FOEFS1JQFMJOF .5-3FOEFS$PNNBOE&ODPEFS .5-$PNNBOE#VGGFS

    .5-3FOEFS1JQFMJOF4UBUF FODPEFSESBX1SJNJUJWFT j NFUBM NFUBM func updateIntensity(to value: Float) { ... } func outputImage(from source: UIImage, intensity: Float = 1.0) -> UIImage { ... } } class ImageFilter {
  5. ؘ੉ఠҙܻ struct ImageState { var crop: Crop? var filter: Filter?

    var correction = Correction() var textStickers = [TextStickerType]() var frame: Frame? var mosaic = [Masking]() var blur: Masking? var resize: Resize? }
  6. ؘ੉ఠҙܻ struct ImageState { var crop: Crop? var filter: Filter?

    var correction = Correction() var textStickers = [TextStickerType]() var frame: Frame? var mosaic = [Masking]() var blur: Masking? var resize: Resize? } ੉޷૑࢚క
  7. enum CropBoxEdge { case none case left case topLeft case

    top case topRight case right case bottomRight case bottom case bottomLeft } TDSPMM7JFX
  8. @objc func cropBoxDragAction(_ gesture: UIPanGestureRecognizer) { let point = gesture.location(in:

    self) switch gesture.state { case .began: self.gestureBeganCropBoxEdge = nearestCropBoxEdge(for: point) case .changed: updateCropBoxFrame(with: point) case ... } }
  9. @objc func cropBoxDragAction(_ gesture: UIPanGestureRecognizer) { let point = gesture.location(in:

    self) switch gesture.state { case .began: self.gestureBeganCropBoxEdge = nearestCropBoxEdge(for: point) case .changed: updateCropBoxFrame(with: point) case ... } }
  10. func updateCropBoxFrame(with gesturePoint: CGPoint) { let point = CGPoint( x:

    max(contentFrame.origin.x, gesturePoint.x), y: max(contentFrame.origin.y, gesturePoint.y) ) switch self.gestureBeganCropBoxEdge { case ... } } @objc func cropBoxDragAction(_ gesture: UIPanGestureRecognizer) { let point = gesture.location(in: self) switch gesture.state { case .began: self.gestureBeganCropBoxEdge = nearestCropBoxEdge(for: point) case .changed: updateCropBoxFrame(with: point) case ... } }
  11. func rotateAngleUpdated(_ angle: CGFloat) { setStraightenAngle(angle * .pi / 180)

    } func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) }
  12. func rotateAngleUpdated(_ angle: CGFloat) { setStraightenAngle(angle * .pi / 180)

    } func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) }
  13. func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) let

    rect = cropBoxFrame let rotatedRect = rect.applying(CGAffineTransform(rotationAngle: angle)) let center = scrollView.center ... scrollView.bounds = ... scrollView.contentOffset = ... scrollView.center = center }
  14. func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) let

    rect = cropBoxFrame let rotatedRect = rect.applying(CGAffineTransform(rotationAngle: angle)) let center = scrollView.center ... scrollView.bounds = ... scrollView.contentOffset = ... scrollView.center = center }
  15. func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) let

    rect = cropBoxFrame let rotatedRect = rect.applying(CGAffineTransform(rotationAngle: angle)) let center = scrollView.center ... scrollView.bounds = ... scrollView.contentOffset = ... scrollView.center = center }
  16. func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) let

    rect = cropBoxFrame let rotatedRect = rect.applying(CGAffineTransform(rotationAngle: angle)) let center = scrollView.center ... scrollView.bounds = ... scrollView.contentOffset = ... scrollView.center = center scrollView.setZoomScale(self.getZoomScaleToBounds(), animated: false) } func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) func getZoomScaleToBounds() -> CGFloat { let scaleW = scrollView.bounds.size.width / imageView.bounds.size.width let scaleH = scrollView.bounds.size.height / imageView.bounds.size.height return max(scaleW, scaleH) }
  17. func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) let

    rect = cropBoxFrame let rotatedRect = rect.applying(CGAffineTransform(rotationAngle: angle)) let center = scrollView.center ... scrollView.bounds = ... scrollView.contentOffset = ... scrollView.center = center scrollView.setZoomScale(self.getZoomScaleToBounds(), animated: false) } func setStraightenAngle(_ angle: CGFloat) { scrollView.transform = CGAffineTransform(rotationAngle: angle) func getZoomScaleToBounds() -> CGFloat { let scaleW = scrollView.bounds.size.width / imageView.bounds.size.width let scaleH = scrollView.bounds.size.height / imageView.bounds.size.height return max(scaleW, scaleH) }
  18. struct ImageState.Crop { ... var scrollViewTransform: CGAffineTransform var scrollViewCenter: CGPoint

    var scrollViewBounds: CGRect var scrollViewContentSize: CGSize var scrollViewContentOffset: CGPoint var scrollViewMinimumZoomScale: CGFloat var scrollViewMaximumZoomScale: CGFloat var scrollViewZoomScale: CGFloat } TDSPMM7JFX
  19. झ౭ழ class ResizableView { @objc private func handleMoveGesture(_ sender: UIGestureRecognizer)

    @objc private func handleResizeGesture(_ sender: UIPanGestureRecognizer) }
  20. झ౭ழ class ResizableView { @objc private func handleMoveGesture(_ sender: UIGestureRecognizer)

    @objc private func handleResizeGesture(_ sender: UIPanGestureRecognizer) } protocol ResizableViewDelegate: AnyObject { func didDeleteButtonTap(_ resizableView: ResizableView) func didResizableViewTap(_ resizableView: ResizableView) func resizableViewBeginMoving(_ resizableView: ResizableView) func resizableViewMoving(_ resizableView: ResizableView, transform: Transform) func resizableViewEndMoving(_ resizableView: ResizableView) func resizableViewAngleChanging(_ resizableView: ResizableView, transform: Transform) func didResizableViewAngleEndChanging(_ resizableView: ResizableView, transform: Transform) }
  21. var invertedTransform: CGAffineTransform { let angle = currentCrop.angle let scale

    = currentCrop.scrollViewZoomScale var transform = CGAffineTransform.identity transform = transform.rotated(by: -angle) transform = transform.scaledBy(x: scale / 1, y: scale / 1) return transform }
  22. var invertedTransform: CGAffineTransform { let angle = currentCrop.angle let scale

    = currentCrop.scrollViewZoomScale var transform = CGAffineTransform.identity transform = transform.rotated(by: -angle) transform = transform.scaledBy(x: scale / 1, y: scale / 1) return transform }
  23. var invertedTransform: CGAffineTransform { let angle = currentCrop.angle let scale

    = currentCrop.scrollViewZoomScale var transform = CGAffineTransform.identity transform = transform.rotated(by: -angle) transform = transform.scaledBy(x: scale / 1, y: scale / 1) return transform }