Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Render SVG to bitmap #335

Closed
kamil3 opened this issue Dec 19, 2019 · 12 comments
Closed

Render SVG to bitmap #335

kamil3 opened this issue Dec 19, 2019 · 12 comments

Comments

@kamil3
Copy link

kamil3 commented Dec 19, 2019

Hi,
I'm using custom decoder for decoding images' data:

struct SVGImageDecoder: Nuke.ImageDecoding {    
    func decode(data: Data, isFinal: Bool) -> Nuke.PlatformImage? {
        //Converting from SVG to Image...
    }
}
ImageDecoderRegistry.shared.register { imageDecodingContext -> ImageDecoding? in
            if imageDecodingContext.request.urlRequest.url?.isSVG ?? false {
                return SVGImageDecoder()
            } else {
                // it takes a default decoder implicitly
                return nil
            }
        }

I noticed that it's firstly getting original data from disk cache (assuming a memory cache is empty) and then it's decoding. My custom decoding takes some time and it's noticeable. Is it any convenient way to store in a disk cache already decoded data in order to speed up loading? I mean, not to convert images every time the app launches (just for the first time).

@kean
Copy link
Owner

kean commented Dec 19, 2019

Hi @kamil3,

That's an interesting use case. It is not supported directly, bu you achieve what you are describing by using DataCache and storing processed images.

pipeline = ImagePipeline {
    $0.dataLoader = DataLoader(configuration: {
        let conf = DataLoader.defaultConfiguration
        conf.urlCache = nil // Disable disk caching built into URLSession
        return conf
    }())

    $0.dataCache = try? DataCache(name: "com.github.kean.Nuke.DataCache")

    $0.isDataCachingForOriginalImageDataEnabled = false
    $0.isDataCachingForProcessedImagesEnabled = true
}

The pipeline will load the image data, decode it, and then re-encode it (see ImageEncoder) and put it on disk.

But there is one issue. The "processed" images need to have at least one processor applied. What you can do is create a processor which does nothing and use it for all your requests:

extension ImageProcessor {
    struct Empty: ImageProcessing {
        public let identifier = "EmptyProcessor"

        func process(image: PlatformImage, context: ImageProcessingContext?) -> PlatformImage? {
            return image
        }
    }
}

This is not ideal, but it should work and it's highly unlikely to break in the future.

@kamil3
Copy link
Author

kamil3 commented Dec 20, 2019

Hi @kean,
Thanks for you response, it looks like it's working. However to make it work completely I had to modify registering a decoder, like below:

ImageDecoderRegistry.shared.register { imageDecodingContext -> ImageDecoding? in
            if imageDecodingContext.request.urlRequest.url?.isSVG ?? false,
                //if it's received from cache it's not SVG data anymore since it was already converted to PNG
                ImageFormat.format(for: imageDecodingContext.data) == nil {
                return SVGImageDecoder()
            } else {
                // it takes a default decoder implicilty
                return nil
            }
        }

I'm using your ImageFormat enum to check the data format to return a proper decoder (I needed to copy it since it's not public).

@kean
Copy link
Owner

kean commented Dec 20, 2019

I think it would be better to extend ImageFormat and add an .svg case with the respective magic numbers.

ImageDecoderRegistry.shared.register { imageDecodingContext -> ImageDecoding? in
    if ImageFormat.format(for: imageDecodingContext.data) == .svg {
        return SVGImageDecoder()
    }
    return nil
}

I was planning to release ImageFormat soon, but I haven't finalized the API for it yet.

@kamil3
Copy link
Author

kamil3 commented Dec 20, 2019

As far as I've been researching magic numbers for SVG do not exist, so probably I could implement another checking if it's SVG format (example thread: https://stackoverflow.com/questions/15136264/how-can-i-say-a-file-is-svg-without-using-a-magic-number).

@kamil3
Copy link
Author

kamil3 commented Dec 21, 2019

Thanks. I'll test it.

Now I have another issue, I noticed 3x more memory usage using this configuration:

pipeline = ImagePipeline {
    $0.dataLoader = DataLoader(configuration: {
        let conf = DataLoader.defaultConfiguration
        conf.urlCache = nil // Disable disk caching built into URLSession
        return conf
    }())

    $0.dataCache = try? DataCache(name: "com.github.kean.Nuke.DataCache")

    $0.isDataCachingForOriginalImageDataEnabled = false
    $0.isDataCachingForProcessedImagesEnabled = true
}

And instead of EmptyProcessor I'm using Resize one:

let size = CGSize(width: 200, height: 200)
ImageRequest(url: url, processors: [ImageProcessor.Resize(size: size)])

It takes about ~5MB for each imageView (I'm rendering them in a collectionView). What is important, when using a default configuration (still with the Resize processor) a memory usage is stable. If it was a clue, whilst decoding I'm returning quite big images, about 1600x1600 px.

Do you have any idea what's causing it?

@kean
Copy link
Owner

kean commented Dec 23, 2019

Not really. In general, downsampling results in lower memory usage. I don't currently have ideas what might be causing this.

@kean
Copy link
Owner

kean commented Jan 6, 2020

Hey, @kamil3. Did you figure out what was the issue with resizing?

@kean kean added the question label Jan 6, 2020
@kamil3
Copy link
Author

kamil3 commented Jan 7, 2020

Hi @kean, I haven't been available until today. For now, I'd probably abandon this solution due to this issue. I'd additionally use prefetching - user experience is slightly better, but still it's not what I would like to have.
If I figure out what's causing the memory issue, I'll write it here.

@kamil3
Copy link
Author

kamil3 commented Jan 7, 2020

What I've investigated so far, encoding image data is causing memory issue: let encodedData = encoder.encode(image: response.image) (line 532 in ImagePipeline). Memory consumption increases and does not decrease. However, I don't know yet why this is happening.
EDIT: pngData and jpgData could be the issue. Similar thread here: https://stackoverflow.com/questions/20244782/uiimagejpegrepresentation-taking-huge-memory
I tried every approach from: https://bencoding.com/2017/03/07/thinking-about-memory-converting-uiimage-to-data-in-swift/, but the memory issue still exists.

@kean
Copy link
Owner

kean commented Mar 19, 2020

Hi, @kamil3, any updates on this issue?

@kean kean changed the title Custom decoder and disk cache Mar 27, 2020
@kean kean added the feature label Mar 27, 2020
@kean
Copy link
Owner

kean commented Mar 27, 2020

I'm going to closes this issue since there is now a lot of outdated information here. I added "Add SVG -> Bitmap decoder" issue in a Trello board.

@kean kean closed this as completed Mar 27, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
2 participants