0

I am working on a photo editing application where I need to squeeze/squash car images to create cute caricatures just like the image attached. This is my very first photo editing application so I don't exactly know what term to put on Google search and even how I can achieve this thing. Any help, guidance, or direction would be a great favor.

Please note this is not related to image resizing.

enter image description here

Following are two more examples.

enter image description here enter image description here

enter image description here

Jabbar
  • 590
  • 1
  • 7
  • 21
  • 1
    This doesn't seem trivial. Maybe an effect like that can be achieved with seem carving. – Frank Rupprecht Sep 16 '22 at 11:32
  • Not really clear what you're trying to do... Do you have a "normal" picture of a car with background, and you want to "squeeze" the car but **not** the background? Or squeeze the entire image? Or, you have images of backgrounds, and images of cars, and you want to add a "squeezed" car image to the background? – DonMag Sep 16 '22 at 14:42
  • @DonMag, I have updated my question with more images for reference. I need to squeeze the car only because I'll be removing the car background before squeezing. – Jabbar Sep 16 '22 at 15:18
  • @Jabbar - yeah, as Frank said... not a trivial task. Search for `erase object from photo source code` -- one result points to this GitHub project: https://github.com/sujaykhandekar/Automated-objects-removal-inpainter ... it's in Python, but may be a good starting point to learn what needs to be done. – DonMag Sep 16 '22 at 17:18
  • @DonMag, I don't need to remove any object. I am already removing the background of the car. Now I just need to squeeze or squash the car. – Jabbar Sep 16 '22 at 17:40
  • @Jabbar -- oh... so you have already "extracted" the car from the background? And you have an image of the car with transparent bounding box? – DonMag Sep 16 '22 at 18:41
  • @DonMag, yes exactly. I now have an image of the car with a transparent bounding box. Next, I need to do is to Squeeze / Squash this car image. – Jabbar Sep 17 '22 at 08:43
  • @Jabbar - ah... you already have *"an image of the car with a transparent bounding box"* ... that makes things very easy. See my answer. – DonMag Sep 17 '22 at 17:07

1 Answers1

1

There are a couple ways to do this...

If we use this as the original image:

enter image description here

it has a transparent bounding box... so it looks like this in an image editing program:

enter image description here

If you are only dealing with how it looks at run-time, you can use a UIImageView with Content Mode: Scale to Fill and then adjust the width of the image view.

If you want to actually scale the UIImage, this simple extension will do the job:

extension UIImage {
    func squeeze(w: CGFloat, h: CGFloat) -> UIImage {
        let newWidth: CGFloat = self.width * w
        let newHeight: CGFloat = self.height * h
        let sz: CGSize = CGSize(width: newWidth, height: newHeight)
        let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(size: sz)
        let img = renderer.image { _ in
            self.draw(in: CGRect(origin: .zero, size: sz))
        }
        return img
    }
}

enter image description here

(the image views have a green border, so we can see the frames).

Here is some example code you can play with:

class ViewController: UIViewController, UITextFieldDelegate {
    
    var origIMG: UIImage!
    
    let imgViewA = UIImageView()
    let imgViewB = UIImageView()
    let imgViewC = UIImageView()
    
    let st = UIStackView()
    
    let statusLabel = UILabel()
    let slider = UISlider()
    
    var bWidth: NSLayoutConstraint!
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        guard let img = UIImage(named: "car1"),
              let bkgImg = UIImage(named: "carsBKG")
        else { return }
        print(img.size)
        origIMG = img
        
        // add a background image view
        let vBKG = UIImageView(image: bkgImg)
        
        st.axis = .vertical
        st.alignment = .center
        st.spacing = 2
        
        [imgViewA, imgViewB, imgViewC].forEach { v in
            v.layer.borderWidth = 2
            v.layer.borderColor = UIColor.green.cgColor
            v.backgroundColor = .clear
            v.image = origIMG
        }
        
        // for A and B, we let the image view frame scale the image
        imgViewA.contentMode = .scaleToFill
        imgViewB.contentMode = .scaleToFill
        // for C we keep the image aspect ratio, because
        //  we're going to modify the UIImage size
        imgViewC.contentMode = .scaleAspectFit
        
        var label: UILabel!
        
        label = UILabel()
        label.text = "Original"
        st.addArrangedSubview(label)
        st.addArrangedSubview(imgViewA)
        st.setCustomSpacing(20.0, after: imgViewA)
        
        label = UILabel()
        label.text = "Modify Image View Frame"
        st.addArrangedSubview(label)
        st.addArrangedSubview(imgViewB)
        st.setCustomSpacing(20.0, after: imgViewB)
        
        label = UILabel()
        label.text = "Modify UIImage"
        st.addArrangedSubview(label)
        st.addArrangedSubview(imgViewC)
        st.setCustomSpacing(20.0, after: imgViewC)
        
        statusLabel.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
        statusLabel.textAlignment = .center
        st.addArrangedSubview(statusLabel)
        
        slider.backgroundColor = .white.withAlphaComponent(0.6)
        slider.addTarget(self, action: #selector(sliderChanged(_:)), for: .valueChanged)
        st.addArrangedSubview(slider)
        st.setCustomSpacing(20.0, after: slider)
        
        vBKG.translatesAutoresizingMaskIntoConstraints = false
        st.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(vBKG)
        view.addSubview(st)
        
        // we're going to modify the 2nd image view width
        bWidth = imgViewB.widthAnchor.constraint(equalTo: st.widthAnchor)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            st.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
            st.leadingAnchor.constraint(equalTo: g.leadingAnchor, constant: 40.0),
            st.trailingAnchor.constraint(equalTo: g.trailingAnchor, constant: -40.0),
            
            // background image view same size/position as stack view
            //  plus 20-points on each side
            vBKG.topAnchor.constraint(equalTo: st.topAnchor, constant: -20.0),
            vBKG.leadingAnchor.constraint(equalTo: st.leadingAnchor, constant: -20.0),
            vBKG.trailingAnchor.constraint(equalTo: st.trailingAnchor, constant: 20.0),
            vBKG.bottomAnchor.constraint(equalTo: st.bottomAnchor, constant: 20.0),
            
            // top image (original) full width, proportional height
            imgViewA.widthAnchor.constraint(equalTo: st.widthAnchor),
            imgViewA.heightAnchor.constraint(equalTo: imgViewA.widthAnchor, multiplier: origIMG.height / origIMG.width),
            
            // 2nd image starts at full width, proportional height
            //  we'll change the image VIEW width
            bWidth,
            imgViewB.heightAnchor.constraint(equalTo: imgViewA.widthAnchor, multiplier: origIMG.height / origIMG.width),
            
            // 3rd image full width, proportional height
            //  we'll change the actual IMAGE
            imgViewC.widthAnchor.constraint(equalTo: st.widthAnchor),
            imgViewC.heightAnchor.constraint(equalTo: imgViewA.widthAnchor, multiplier: origIMG.height / origIMG.width),
            
            slider.widthAnchor.constraint(equalTo: st.widthAnchor),
        ])
        
        // start slider at 100%
        slider.value = 1.0
        
        statusLabel.text = "100%"
        
        // let's make the labels readable
        st.arrangedSubviews.forEach { vSub in
            if let v = vSub as? UILabel {
                v.textColor = .white
                v.backgroundColor = .black.withAlphaComponent(0.4)
                v.textAlignment = .center
                v.widthAnchor.constraint(equalTo: st.widthAnchor).isActive = true
            }
        }

    }
    
    @objc func sliderChanged(_ sender: UISlider) {
        let pct = CGFloat(sender.value)
        // let's use a minimum scale of 0.5%
        if pct < 0.05 { return }
        
        // update imgViewB's width anchor as a percentage
        bWidth.isActive = false
        bWidth = imgViewB.widthAnchor.constraint(equalTo: st.widthAnchor, multiplier: pct)
        bWidth.isActive = true
        
        // set imgViewC's image to a NEW rendered UIImage
        //  we're scaling the width, keeping the original height
        imgViewC.image = origIMG.squeeze(w: pct, h: 1.0)
        
        statusLabel.text = "\(Int(pct * 100))%"
    }
    
}


extension UIImage {
    func squeeze(w: CGFloat, h: CGFloat) -> UIImage {
        let newWidth: CGFloat = self.width * w
        let newHeight: CGFloat = self.height * h
        let sz: CGSize = CGSize(width: newWidth, height: newHeight)
        let renderer: UIGraphicsImageRenderer = UIGraphicsImageRenderer(size: sz)
        let img = renderer.image { _ in
            self.draw(in: CGRect(origin: .zero, size: sz))
        }
        return img
    }
}

When running, it looks like this - dragging the slider sets the Widths to a percentage of the original:

enter image description here

enter image description here

enter image description here

DonMag
  • 69,424
  • 5
  • 50
  • 86
  • You did a great job with your answer here @DonMag, but looking at the references that were given in the question, the wheels there remained round despite the car being squished. In addition to your answer, I would agree with Frank Rupprecht's comment that modifying the image with seam carving would give you good results here. – jhwblender Feb 08 '23 at 14:46
  • @jhwblender - true, the wheels scaled with the rest of the image. To maintain aspect ratio on **parts** of the image (the wheels), we could use shape-recognition or manual selections, then cut those shapes, scale the image, paste those shapes (with edge blending). Bit more complex of a task. The OP didn't give us much to go on... would be helpful to know what tool he used to create the images in the first place, and explain whether it was fully automated or required manual adjustments. – DonMag Feb 08 '23 at 15:31