Mam UIImage, który zawiera kształt; reszta jest przejrzysta. Chciałbym uzyskać kolejny UIImage, wycinając tak dużo przezroczystej części, jak to możliwe, nadal zachowując wszystkie nieprzejrzyste piksele - podobnie do funkcji automatycznego przycinania w GIMP. Jak miałbym to zrobić?Jak autocropować UIImage?
Odpowiedz
Takie podejście może być nieco bardziej inwazyjne niż to, na co liczyliśmy, ale wykonuje to zadanie. To, co robię, to tworzenie kontekstu bitmapowego dla UIImage, uzyskiwanie wskaźnika do danych surowego obrazu, a następnie przesiewanie go w poszukiwaniu nieprzejrzystych pikseli. Moja metoda zwraca CGRect, którego używam do utworzenia nowego obiektu UII.
- (CGRect)cropRectForImage:(UIImage *)image {
CGImageRef cgImage = image.CGImage;
CGContextRef context = [self createARGBBitmapContextFromImage:cgImage];
if (context == NULL) return CGRectZero;
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
CGRect rect = CGRectMake(0, 0, width, height);
CGContextDrawImage(context, rect, cgImage);
unsigned char *data = CGBitmapContextGetData(context);
CGContextRelease(context);
//Filter through data and look for non-transparent pixels.
int lowX = width;
int lowY = height;
int highX = 0;
int highY = 0;
if (data != NULL) {
for (int y=0; y<height; y++) {
for (int x=0; x<width; x++) {
int pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */;
if (data[pixelIndex] != 0) { //Alpha value is not zero; pixel is not transparent.
if (x < lowX) lowX = x;
if (x > highX) highX = x;
if (y < lowY) lowY = y;
if (y > highY) highY = y;
}
}
}
free(data);
} else {
return CGRectZero;
}
return CGRectMake(lowX, lowY, highX-lowX, highY-lowY);
}
Metoda tworzenia kontekstu mapy bitowej
- (CGContextRef)createARGBBitmapContextFromImage:(CGImageRef)inImage {
CGContextRef context = NULL;
CGColorSpaceRef colorSpace;
void *bitmapData;
int bitmapByteCount;
int bitmapBytesPerRow;
// Get image width, height. We'll use the entire image.
size_t width = CGImageGetWidth(inImage);
size_t height = CGImageGetHeight(inImage);
// Declare the number of bytes per row. Each pixel in the bitmap in this
// example is represented by 4 bytes; 8 bits each of red, green, blue, and
// alpha.
bitmapBytesPerRow = (width * 4);
bitmapByteCount = (bitmapBytesPerRow * height);
// Use the generic RGB color space.
colorSpace = CGColorSpaceCreateDeviceRGB();
if (colorSpace == NULL) return NULL;
// Allocate memory for image data. This is the destination in memory
// where any drawing to the bitmap context will be rendered.
bitmapData = malloc(bitmapByteCount);
if (bitmapData == NULL)
{
CGColorSpaceRelease(colorSpace);
return NULL;
}
// Create the bitmap context. We want pre-multiplied ARGB, 8-bits
// per component. Regardless of what the source image format is
// (CMYK, Grayscale, and so on) it will be converted over to the format
// specified here by CGBitmapContextCreate.
context = CGBitmapContextCreate (bitmapData,
width,
height,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
kCGImageAlphaPremultipliedFirst);
if (context == NULL) free (bitmapData);
// Make sure and release colorspace before returning
CGColorSpaceRelease(colorSpace);
return context;
}
i wreszcie dostać nowego przyciętego UIImage od zwracanej CGRect:
CGRect newRect = [self cropRectForImage:oldImage];
CGImageRef imageRef = CGImageCreateWithImageInRect(oldImage.CGImage, newRect);
UIImage *newImage = [UIImage imageWithCGImage:imageRef];
CGImageRelease(imageRef);
złapałem trochę tego kodu z this very useful article. Mam nadzieję, że to pomoże!
Swift Wersja:
extension UIImage {
func cropRect() -> CGRect {
let cgImage = self.CGImage!
let context = createARGBBitmapContextFromImage(cgImage)
if context == nil {
return CGRectZero
}
let height = CGFloat(CGImageGetHeight(cgImage))
let width = CGFloat(CGImageGetWidth(cgImage))
let rect = CGRectMake(0, 0, width, height)
CGContextDrawImage(context, rect, cgImage)
let data = UnsafePointer<CUnsignedChar>(CGBitmapContextGetData(context))
if data == nil {
return CGRectZero
}
var lowX = width
var lowY = height
var highX: CGFloat = 0
var highY: CGFloat = 0
//Filter through data and look for non-transparent pixels.
for (var y: CGFloat = 0 ; y < height ; y++) {
for (var x: CGFloat = 0; x < width ; x++) {
let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
if data[Int(pixelIndex)] != 0 { //Alpha value is not zero pixel is not transparent.
if (x < lowX) {
lowX = x
}
if (x > highX) {
highX = x
}
if (y < lowY) {
lowY = y
}
if (y > highY) {
highY = y
}
}
}
}
return CGRectMake(lowX, lowY, highX-lowX, highY-lowY)
}
}
Metoda tworzenia kontekstu mapy bitowej
func createARGBBitmapContextFromImage(inImage: CGImageRef) -> CGContextRef? {
let width = CGImageGetWidth(inImage)
let height = CGImageGetHeight(inImage)
let bitmapBytesPerRow = width * 4
let bitmapByteCount = bitmapBytesPerRow * height
let colorSpace = CGColorSpaceCreateDeviceRGB()
if colorSpace == nil {
return nil
}
let bitmapData = malloc(bitmapByteCount)
if bitmapData == nil {
return nil
}
let context = CGBitmapContextCreate (bitmapData,
width,
height,
8, // bits per component
bitmapBytesPerRow,
colorSpace,
CGImageAlphaInfo.PremultipliedFirst.rawValue)
return context
}
i wreszcie dostać nowego przyciętego UIImage od zwracanej CGRect:
let image = // UIImage Source
let newRect = image.cropRect()
if let imageRef = CGImageCreateWithImageInRect(image.CGImage!, newRect) {
let newImage = UIImage(CGImage: imageRef)
// Use this new Image
}
Twoje rozwiązanie uratuje mi życie. Wielkie dzięki!!! –
Swift 3 (nie jest to rozbudowa UIImage, potrzebowałem go do innej klasy) może zaoszczędzić czas dla kogoś:
class EditImage {
static func cropRect(_ image: UIImage) -> CGRect {
let cgImage = image.cgImage
let context = createARGBBitmapContextFromImage(inImage: cgImage!)
if context == nil {
return CGRect.zero
}
let height = CGFloat(cgImage!.height)
let width = CGFloat(cgImage!.width)
let rect = CGRect(x: 0, y: 0, width: width, height: height)
context?.draw(cgImage!, in: rect)
//let data = UnsafePointer<CUnsignedChar>(CGBitmapContextGetData(context))
let data = context?.data?.assumingMemoryBound(to: UInt8.self)
if data == nil {
return CGRect.zero
}
var lowX = width
var lowY = height
var highX: CGFloat = 0
var highY: CGFloat = 0
let heightInt = Int(height)
let widthInt = Int(width)
//Filter through data and look for non-transparent pixels.
for y in (0 ..< heightInt) {
let y = CGFloat(y)
for x in (0 ..< widthInt) {
let x = CGFloat(x)
let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
if data?[Int(pixelIndex)] != 0 { //Alpha value is not zero pixel is not transparent.
if (x < lowX) {
lowX = x
}
if (x > highX) {
highX = x
}
if (y < lowY) {
lowY = y
}
if (y > highY) {
highY = y
}
}
}
}
return CGRect(x: lowX, y: lowY, width: highX - lowY, height: highY - lowY)
}
static func createARGBBitmapContextFromImage(inImage: CGImage) -> CGContext? {
let width = inImage.width
let height = inImage.height
let bitmapBytesPerRow = width * 4
let bitmapByteCount = bitmapBytesPerRow * height
let colorSpace = CGColorSpaceCreateDeviceRGB()
if colorSpace == nil {
return nil
}
let bitmapData = malloc(bitmapByteCount)
if bitmapData == nil {
return nil
}
let context = CGContext (data: bitmapData,
width: width,
height: height,
bitsPerComponent: 8, // bits per component
bytesPerRow: bitmapBytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
return context
}
}
dzięki za kod @ Danny182, jednak na linii powrotnej było literówka (highX - lowY) -> powinno być highX - lowX –
może dlatego nie działało idealnie. Wypróbuję to. TY – Danny182
CGColorSpaceCreateDeviceRGB() nie zwraca opcjonalnego, zerowego wyboru nie jest konieczne. –
Lepsza od @ odpowiedź Danny182, w Dodałem też white-space (wszystkie piksele jaśniejsze niż 0xe0e0e0) przycinania moja własna potrzeba.
Zastosowanie:
let newimage = UIImage(named: "XXX")!.trim()
import UIKit
extension UIImage {
func trim() -> UIImage {
let newRect = self.cropRect
if let imageRef = self.cgImage!.cropping(to: newRect) {
return UIImage(cgImage: imageRef)
}
return self
}
var cropRect: CGRect {
let cgImage = self.cgImage
let context = createARGBBitmapContextFromImage(inImage: cgImage!)
if context == nil {
return CGRect.zero
}
let height = CGFloat(cgImage!.height)
let width = CGFloat(cgImage!.width)
let rect = CGRect(x: 0, y: 0, width: width, height: height)
context?.draw(cgImage!, in: rect)
//let data = UnsafePointer<CUnsignedChar>(CGBitmapContextGetData(context))
guard let data = context?.data?.assumingMemoryBound(to: UInt8.self) else {
return CGRect.zero
}
var lowX = width
var lowY = height
var highX: CGFloat = 0
var highY: CGFloat = 0
let heightInt = Int(height)
let widthInt = Int(width)
//Filter through data and look for non-transparent pixels.
for y in (0 ..< heightInt) {
let y = CGFloat(y)
for x in (0 ..< widthInt) {
let x = CGFloat(x)
let pixelIndex = (width * y + x) * 4 /* 4 for A, R, G, B */
if data[Int(pixelIndex)] == 0 { continue } // crop transparent
if data[Int(pixelIndex+1)] > 0xE0 && data[Int(pixelIndex+2)] > 0xE0 && data[Int(pixelIndex+3)] > 0xE0 { continue } // crop white
if (x < lowX) {
lowX = x
}
if (x > highX) {
highX = x
}
if (y < lowY) {
lowY = y
}
if (y > highY) {
highY = y
}
}
}
return CGRect(x: lowX, y: lowY, width: highX - lowX, height: highY - lowY)
}
func createARGBBitmapContextFromImage(inImage: CGImage) -> CGContext? {
let width = inImage.width
let height = inImage.height
let bitmapBytesPerRow = width * 4
let bitmapByteCount = bitmapBytesPerRow * height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bitmapData = malloc(bitmapByteCount)
if bitmapData == nil {
return nil
}
let context = CGContext (data: bitmapData,
width: width,
height: height,
bitsPerComponent: 8, // bits per component
bytesPerRow: bitmapBytesPerRow,
space: colorSpace,
bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue)
return context
}
}
Działa idealnie! Jedną z sugestii byłoby zastosowanie konwencji nazewnictwa Swift i wywołanie funkcji 'trim()' 'trimmed()', ponieważ zwraca się nowy obiekt. Nazewnictwo będzie poprawne, jeśli zwrócisz 'void' i zmodyfikujesz istniejącą instancję UIImage. – blackjacx
@Daddycat Tan Yin See, fajne rozwiązanie - jak tu zarządzać pamięcią? czy opieka nad malloc jest? – shadowf
Swift 4
extension UIImage {
func cropAlpha() -> UIImage {
let cgImage = self.cgImage!;
let width = cgImage.width
let height = cgImage.height
let colorSpace = CGColorSpaceCreateDeviceRGB()
let bytesPerPixel:Int = 4
let bytesPerRow = bytesPerPixel * width
let bitsPerComponent = 8
let bitmapInfo: UInt32 = CGImageAlphaInfo.premultipliedLast.rawValue | CGBitmapInfo.byteOrder32Big.rawValue
guard let context = CGContext(data: nil, width: width, height: height, bitsPerComponent: bitsPerComponent, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: bitmapInfo),
let ptr = context.data?.assumingMemoryBound(to: UInt8.self) else {
return self
}
context.draw(self.cgImage!, in: CGRect(x: 0, y: 0, width: width, height: height))
var minX = width
var minY = height
var maxX: Int = 0
var maxY: Int = 0
for x in 1 ..< width {
for y in 1 ..< height {
let i = bytesPerRow * Int(y) + bytesPerPixel * Int(x)
let a = CGFloat(ptr[i + 3])/255.0
if(a>0) {
if (x < minX) { minX = x };
if (x > maxX) { maxX = x };
if (y < minY) { minY = y};
if (y > maxY) { maxY = y};
}
}
}
let rect = CGRect(x: CGFloat(minX),y: CGFloat(minY), width: CGFloat(maxX-minX), height: CGFloat(maxY-minY))
let imageScale:CGFloat = self.scale
let croppedImage = self.cgImage!.cropping(to: rect)!
let ret = UIImage(cgImage: croppedImage, scale: imageScale, orientation: self.imageOrientation)
return ret;
}
}
- 1. Jak skopiować UIImage?
- 2. Jak utworzyć czarną UIImage?
- 3. Jak ustawić ramkę UIImage
- 4. iPhone SDK - Jak narysować UIImage na inną UIImage?
- 5. Dodaj UIImage na wierzchu innej UIImage
- 6. jak przekonwertować CVImageBufferRef na UIImage
- 7. Jak uzyskać rozmiar (KB) UIImage
- 8. Jak serializować UIImage do JSON?
- 9. Jak zapisać UIImage do pliku?
- 10. Jak utworzyć UIImage z UIBezierPath
- 11. Jak przekonwertować UIImage do CVPixelBuffer
- 12. Jak renderować SKNode do UIImage
- 13. maskowanie UIImage
- 14. UIImage i resizableImageWithCapInsets Swift
- 15. Uzyskaj kolor piksela UIImage
- 16. Konwersja UIImage w tekstury
- 17. Dodaj UIImage do CCSprite?
- 18. sposób użycia [UIImage resizableImageWithCapInsets:]
- 19. Utwórz UIImage z MKMapView
- 20. UIImage initWithContentsOfFile nie działa
- 21. UIImage Rotacja niestandardowych stopni
- 22. Dodaj tekst do UIImage
- 23. Konwersja PDF do UIImage
- 24. UIImage meta dane
- 25. Obracanie i przycinanie UIImage
- 26. Przechwyć UIView jako UIImage
- 27. setAnchorPoint dla UIImage?
- 28. UIImage z CGImageRef
- 29. UIImage z regionu UIView
- 30. Konwersja UIImage do CMSampleBufferRef
Dzięki. Działa świetnie ... :) – Thampuran