【问题标题】:How can I create a scaled image from a huge array of values如何从大量值中创建缩放图像
【发布时间】:2017-12-22 03:52:56
【问题描述】:

我正在从使用媒体播放器录制时从麦克风获取的音量值列表创建波形图像。

这对于几分钟长的录音非常有用,但当录音长约一小时时,我会收到内存不足错误。

我知道这个错误来自我试图创建位图图像的代码,宽度(以像素为单位)是数组的长度,高度始终为 200 像素。

请有人帮我创建一个方法,该方法可以接收 n 长度的大型 int 值数组(从 0 到 100),并创建一个缩放的位图图像?

我知道我最好通过较小的数组块运行并从中创建一个缩放图像,然后可能以某种方式将其附加到主缩放图像上,但我正在努力。

这是我的代码:

    public void generateWaveFormImage() {
    try {
        mBitmap = Bitmap.createBitmap(waveValues.size(), 200, Bitmap.Config.RGB_565);
        canvas = new Canvas(mBitmap);

        for(int i=0; i<waveValues.size(); i++) {
            canvas.drawLine(i, 100, i, 100-(int)waveValues.get(i), paint);
            canvas.drawLine(i, 100, i, 100 + (int) waveValues.get(i), paint);
        }

        Bitmap resized = Bitmap.createScaledBitmap(mBitmap, 760, 200, true);
        FileManager.saveImage(mShortNameWaveImage, resized);

    } catch(Exception e) {}
    waveValues = new ArrayList();
}

这对于较小的记录非常有用,即当 waveValues 数组中存储了 2000 个值时。

这是抛出内存不足错误的行

    mBitmap = Bitmap.createBitmap(waveValues.size(), 200, Bitmap.Config.RGB_565);

我想添加到目前为止我尝试过的内容。

假设waveValues 数组的长度为 3500。

我取前 1000 个值,创建一个 1000 x 200 的图像并将值写入该图像,然后缩小到 760 X 200 并保存。

然后我获取接下来的 1000 个值并执行相同的操作,然后将它们附加在一起,并将图像重新缩放到 760 x 200。

然后我获取接下来的 1000 个值,执行相同的操作,并附加到主图像上,重新缩放到 760,然后对最后 500 个值执行相同的操作。

这导致创建了一个错误缩放的图像,前2个图像缩放太多,第三个图像正确,最后一个图像被缩放到更大的图像,这使得波形非常奇怪和美妙。

【问题讨论】:

    标签: java android arrays canvas bitmap


    【解决方案1】:

    不仅大型位图会导致内存问题,而且渲染每个样本也会做很多不必要的工作,因为您只会一遍又一遍地在相同像素的顶部绘制。

    处理此问题的最佳方法是首先为位图设置大小限制。在下面的示例中,即 2000 像素,当样本计数小于此值时,位图将调整为样本计数。如果样本数大于位图将被限制为 2000 像素。

    对于每个像素列将覆盖许多样本的长样本,您只需绘制该列所覆盖样本的最大值。所以保留一个变量x,即当前样本转换为像素位置。

    x 的值与之前的迭代 x 相同时,只需通过 max = Math.max(waveValues.get(i)); 获得最大值

    当 x 值更改为下一列时,您将呈现最大值。该最大值是前一列的最大样本值。然后将最大值重置为当前样本。

    在循环结束时,您需要渲染最后一列。

    因为这只会在每列的样本数大于大约 1.5(估计值)时为您节省时间,所以此时您应该只使用该最大值方法。在示例中,我将其设置为每列 2 个或更多样本。

    public void generateWaveFormImage() {
        try {
            int samples = waveValues.size();
            int width = Math.min(2000, samples);
            mBitmap = Bitmap.createBitmap(width, 200, Bitmap.Config.RGB_565);
            canvas = new Canvas(mBitmap);
            double samplesPerColumn = samples / width;
            int x;
            // if not downsampling much then render as normal
            if(samplesPerColumn < 2.0){
                for(int i=0; i < waveValues.size(); i++) {
                    x = (int)(i / samplesPerColumn);
                    canvas.drawLine(x, 100- (int) waveValues.get(i), x, 100 + (int) waveValues.get(i), paint);
                }
            }else{  // if down sampling then render the max value for every samplesPerColumn
                double max = 0;
                int lastX = -1;
                for(int i=0; i<waveValues.size(); i++) {
                    x = (int)(i / samplesPerColumn);
                    if(x != lastX){
                        if(i != 0){
                           canvas.drawLine(x - 1, 100 - (int) max, x - 1, 100 + (int) max, paint);
                        }
                        max = waveValues.get(i);
                    }else{
                        max = Math.max(max,waveValues.get(i)); 
                    }
                    lastX = x;
                 }
                 canvas.drawLine(x - 1, 100 - (int) max, x - 1, 100 + (int) max, paint);
            }
    
    
    
            Bitmap resized = Bitmap.createScaledBitmap(mBitmap, 760, 200, true);
            FileManager.saveImage(mShortNameWaveImage, resized);
    
        } catch(Exception e) {}
    
    }
    

    【讨论】:

      【解决方案2】:

      内存中的位图未经压缩存储,在 RGB_565 中,每点 2 个字节,如果您的图像是 2000 宽度 * 200 高度 = 400k 点 *2 = 800 KB 数据 - 您应该在更大的图像上出现 OOM 错误. 无论如何,您必须在绘制之前重新缩放数据,而不是之后 - 绘制完整尺寸的点然后传递特定于图像的重新缩放方法有什么用?

      double scaleFactor = (double) 760/waveValues.size(); //This will fit all your points inside 760px resolution
      for(int i=0; i<waveValues.size(); i++) {
              canvas.drawLine((int) (i*scaleFactor), 100, (int) (i*scaleFactor), 100 - (int)waveValues.get(i), paint);
              canvas.drawLine((int) (i*scaleFactor), 100, (int) (i*scaleFactor), 100 + (int)waveValues.get(i), paint);
          }
      

      当然,您可以通过一次绘制操作将速度提高一倍:

      double scaleFactor = (double) 760/waveValues.size(); //This will fit all your points inside 760px resolution
      for(int i=0; i<waveValues.size(); i++) {
              canvas.drawLine((int) (i*scaleFactor), 100 - (int)waveValues.get(i), (int) (i*scaleFactor), 100 + (int)waveValues.get(i), paint);           
          }
      

      现在,如果你有 76 个点,每条线的宽度将是 10 像素。

      如果你有 7600 个点,每条线的宽度为 0.1px,所以 1px 中有 10 条线

      您的图像将始终保持在 760x200 内

      顺便说一句,您应该确保您的值始终在 0..100 范围内,否则您会出现剪裁(线条会落在图片之外)

      【讨论】:

      • 非常感谢您,您是救生员!!!我不敢相信它是那么简单!我想它需要一些新鲜的眼睛,所以非常感谢,我非常感谢。您对 RGB_565 的看法是正确的,最初我将其设置为 ARGB_8888,这会导致各种内存问题,所以我将其更改为 RGB_565,但有些人仍然遇到问题,特别是如果 waveValues 数组中有 500,000 个值时.再次感谢,亲切的问候,尼尔
      • 在 win32 上 jvm32 可以访问大约 1.4-1.8 Gb,在 linux32 上大约是 2Gb,在 win64 上 jvm32 可以访问大约 4Gb 但有一些限制。如果您在一个线程/一个对象中需要超过 1.4Gb 的 RAM,那么您应该考虑使用 jvm64 的任何方式。虽然 200px 高度中的 500k 点在 565 格式中仅为 100mp = 200Mb,但不同的重新缩放功能可以创建大缓冲区,消耗内存,还确保您处理不需要的位图/画布 - 即使位图的单个内存泄漏也意味着您的 ram 将被填满瞬间。
      猜你喜欢
      • 1970-01-01
      • 2016-07-31
      • 2012-11-14
      • 2010-11-07
      • 2013-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-03
      相关资源
      最近更新 更多