Добро пожаловать в форум, Guest  >>   Войти | Регистрация | Поиск | Правила | В избранное | Подписаться
Все форумы / WinForms, .Net Framework Новый топик    Ответить
 А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Я делаю как.
1) выгружаю исходный TIFF в многофреймовый битмап m_Bitmap
m_Bitmap = New Bitmap(FileName)

и могу с ним работать, фокус на отдельную страницу устанавливается так
m_Bitmap.SelectActiveFrame(FrameDimension.Page, nFrame - 1)

ну и т.д.
2) создаю другой битмап m_CoverBitmap и туда рисую в соответствии с разрешением первого то что хочу сделать первым фреймом
(это быстро) -он пока что в памяти
3) а финальный TIFF делается грубо говоря, сначала пиханием в него вновь созданного m_CoverBitmap, а затем перерисовкой в него фреймов исходного m_Bitmap последовательно, приведу кусок кода.

    ' Get an ImageCodecInfo object that represents the TIFF codec.
    Dim myImageCodecInfo As ImageCodecInfo = GetEncoderInfo("image/tiff")
    ' Create an EncoderParameters object. An EncoderParameters object has an array of EncoderParameter 
    ' objects. In this case, there are 2 EncoderParameter objects in the array.
    Dim myEncoderParameters As EncoderParameters = New EncoderParameters(2)
    myEncoderParameters.Param(0) = New EncoderParameter(Encoder.Compression, Fix(eTifCompression))

    ReDim nBitmap(0 To endF) 'используем элементы startF-endF, в оригинале ReDim nBitmap(startF To endF)

    '========Cover Page========

    If HasCover Then
      'сохранение в TIFF титульной страницы
      Try
        ' save first image in TIFF
        myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.MultiFrame))
        m_CoverBitmap.Save(FileName, myImageCodecInfo, myEncoderParameters)
      Catch
      End Try
    Else
      Return False 'при отсутствии CoverPage ничего не сохраняем и возвращаем False
    End If
    '========Cover Page========

    For i As Integer = startF To endF
      nSrcWidth = FrameWidth(i)
      nSrcHeight = FrameHeight(i)
      With bmiDst.bmiHeader
        .biSize = Marshal.SizeOf(bmiDst.bmiHeader)
        .biWidth = nSrcWidth
        .biHeight = CInt(nSrcHeight * dHeightScale)
        .biPlanes = 1
        .biBitCount = 1
        .biCompression = BI_RGB
      End With
      With bmiDst.bmiColors1
        .rgbBlue = 255
        .rgbGreen = 255
        .rgbRed = 255
      End With
      hbmDst = CreateDIBSection(IntPtr.Zero, bmiDst, DIB_RGB_COLORS, pBits, IntPtr.Zero, 0)

      hdcDst = CreateCompatibleDC(IntPtr.Zero)
      hbmOldDst = SelectObject(hdcDst, hbmDst)

      bitmapSrc = Frame(i)
      If Not bitmapSrc Is Nothing Then
        Using gr As Graphics = Graphics.FromHdc(hdcDst)
          gr.DrawImage(bitmapSrc, 0, 0, nSrcWidth, CInt(nSrcHeight * dHeightScale))
        End Using
        bitmapSrc.Dispose()
      End If

      SelectObject(hdcDst, hbmOldDst) : hbmOldDst = IntPtr.Zero
      DeleteDC(hdcDst) : hdcDst = IntPtr.Zero

      Try
        nBitmap(i) = Bitmap.FromHbitmap(hbmDst)
        If (DpiX > 0) And (DpiY > 0) Then nBitmap(i).SetResolution(DpiX, DpiY)
        ' Add more images in TIFF (lBitmap (0))
        myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.FrameDimensionPage))
        m_CoverBitmap.SaveAdd(nBitmap(i), myEncoderParameters)
        nBitmap(i).Dispose() : nBitmap(i) = Nothing
      Catch
      End Try
      DeleteObject(hbmDst) : hbmDst = IntPtr.Zero : pBits = IntPtr.Zero
    Next i

    ' закрываем многокадровый файл
    myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.Flush))
    Try : m_CoverBitmap.SaveAdd(myEncoderParameters) : Catch : End Try

    Erase nBitmap


Суть в чем, я по сути перерисовываю исходный TIFF заново в новый на довольно низком GDI+ уровне,
что для 1-2 страниц несущественно по времени, а если страниц уже скажем 5-10-20 то операция накладная.
Ну, оно конечно работает но долго.

Мне по сути надо сдвинуть имеющиеся страницы в конец файла, ничего вообще в них не меняя, ни компрессии, ни размеров, ни разрешения, ни цветности (ч/б, 2 цвета), и вставить первую страницу. Свойства исх. файла (разрешение, компрессия) я вычислять умею - это быстро - чтобы правильно сделать вставляемую в начало новую страницу, которая у меня в памяти сидит как m_CoverBitmap.
А вот как их быстро срастить?

На сколько я понимаю, когда я делаю m_Bitmap = New Bitmap(FileName), я теряю "свойства" исходного файла, и вынужден ваять этот файл по сути рисуя заново, на что уходит время.

М.б. сумбурно объясняю, но как смог чтобы не приводить простыни кодов и не сбивать с толку.
В общем я хочу эту страничку "дорисовать" быстро.

Ну или поставим задачу так:
Есть 2 независимых TIFF с одинаково-совместимыми свойствами (компрессия, разрешение, размеры и т.д.), как бы их срастить в такой же "формат" в единый файл?
29 авг 18, 06:01    [21657058]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Изопропил
Member

Откуда:
Сообщений: 31113
Формат ttff прост и документирован, склеить несложно
29 авг 18, 12:10    [21657438]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Изопропил,

Я похоже лишние операции делаю
Т.е. заново создаю структуру битмапа, и занимаюсь перерисовкой (см. фрагмент кода выше)
      bitmapSrc = Frame(i)
      If Not bitmapSrc Is Nothing Then
        Using gr As Graphics = Graphics.FromHdc(hdcDst)
          gr.DrawImage(bitmapSrc, 0, 0, nSrcWidth, CInt(nSrcHeight * dHeightScale))
        End Using
        bitmapSrc.Dispose()
      End If

А это время-затратная операция, тогда как в моей текущей задаче просто достаточно дописать имеющиеся фреймы в новый tiff.

Уши растут вот отсюда:
Корректная конвертация ч/б TIFF(200х100)->BMP,JPG и т.п.
Но там мне требовалось сжимать-растягивать картинку в связи с изменением разрешения, и простое пересохранение приводило к изменению глубины цветности с 2 цветов (ч/б) на 32-бит.
Если мне в сращиваемых картинках ничего менять не надо, то все итак вроде итак будет OK и все эти свистопляски с перекачкой одного битмапа в другой не нужны, и будет на порядки быстрее.

Попробую сделать аккурато, посмотрю.
29 авг 18, 17:04    [21657920]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Изопропил
Member

Откуда:
Сообщений: 31113
Дмитрий77,

Приписать в вставляемую страницу в конец файла, список IFD скорректировать, смещения пересчитать

Ничего сложного
29 авг 18, 20:03    [21658066]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Изопропил
Приписать в вставляемую страницу в конец файла, список IFD скорректировать, смещения пересчитать
Изопропил,
Ну ты предлагаешь сращивать, копаясь в файлах на байтовом уровне и знании формата (c SFF так как-то возился).
Зачем, если можно сделать штатными даже .Net классами, не вдаваясь в низкоуровневые подробности.

Я без GDI +/- все одно не обойдусь, титульный лист я генерирую на ходу.
1) Т.е. есть файл, про него нужно знать: (1)разрешение (2) compression -это я знаю как, узнал
2) Теперь создаю CoverPage зная (1)разрешение , кот. узнал на первом шаге.
3) Сохраняю CoverPage и добавляю фреймы из исходного файла. Делаю это используя (2) compression , кот. узнал на первом шаге.

У меня класс, который работает с мульти-фреймами писан давно, изначально еще на VB6 и шибко путанный, решил нахаляву добавить туда этот функционал и огреб. Т.е. результат то получил, но много лишних операций (кусок кода в первом посте). В итоге "очень долго".
Но счас все сделал без лишнего, получилось быстро (условно "мгновенно"), даже если подсовываю CoverPage под 50 страниц.

1. взял исх. файл, воткнул его в Bitmap, также узнал его (1)разрешение (2) compression , не суть как, умею.
m_Bitmap = New Bitmap(FileName)


2. сваял Cover Page, с учетом разрешения исходного файла (всего два варианта 204x196 или 204x98, им соответствуют размеры соответственно 1728x2339 и 1728x1169 для A4,PORTRAIT).
Здесь без GDI да и без API не обойтись и структуру BITMAPINFO2 надо задавать.
Использую FaxPrintCoverPageW function, причем недокументированно (без предварительного вызова FaxStartPrintJob как они там вещают в MSDN), моя задача "угнать" картинку в свой hdc, который я готовлю через CreateDC+CreateCompatibleDC. Ну в общем справился, на выходе имею битмап m_CoverBitmap, совместимый с исходным файлом.
+
  Public Sub AddCover( _
   Optional ByVal RecFaxNumber As String = vbNullString, _
   Optional ByVal RecName As String = vbNullString, _
   Optional ByVal UseServerCoverPage As Boolean = True, _
   Optional ByVal CoverPageName As String = "generic", _
   Optional ByVal Subject As String = vbNullString, _
   Optional ByVal Note As String = vbNullString, _
   Optional ByVal SdrName As String = vbNullString, _
   Optional ByVal SdrFaxNumber As String = vbNullString, _
   Optional ByVal SdrCompany As String = vbNullString, _
   Optional ByVal SdrDepartment As String = vbNullString, _
   Optional ByVal SdrTitle As String = vbNullString, _
   Optional ByVal SdrOfficeLocation As String = vbNullString, _
   Optional ByVal SdrHomePhone As String = vbNullString, _
   Optional ByVal SdrOfficePhone As String = vbNullString, _
   Optional ByVal SdrAddress As String = vbNullString, _
   Optional ByVal defResolution As String = "204x196")

    ClearCover()

    '204x196,1728x2339 (A4,PORTRAIT)
    '204x98,1728x1169 (A4,PORTRAIT)
    Dim bCreateCover As Boolean = True

    Dim y_Res As Single
    If FrameCount > 0 Then
      y_Res = VerticalResolution
    Else
      If defResolution = "204x196" Then : y_Res = 196 : Else : y_Res = 98 : End If
    End If

    Dim FaxPrinter = My_GetLocalFaxPrinterName() ' "Fax"

    Dim dm As New DEVMODE 'сюда получаем тек. структуру (главное ее шаблон правильный)
    If GetPrinterDevMode(FaxPrinter, dm) = False Then 'ну значит факс хозяйство не установлено, чего дальше стараться
      bCreateCover = False
      Exit Sub
    End If
    'подправим (не меняя дефолтные тек. настройки)
    dm.dmPrintQuality = 200
    If y_Res = 196 Then : dm.dmYResolution = 200 : Else : dm.dmYResolution = 100 : End If
    dm.dmOrientation = DMORIENT_PORTRAIT
    dm.dmPaperSize = DMPAPER_A4

    Dim bmiDst As BITMAPINFO2
    Dim pBits As IntPtr
    Dim hbmDst As IntPtr
    Dim hdcDst As IntPtr
    Dim hbmOldDst As IntPtr
    '========Cover Page========

    With bmiDst.bmiHeader
      .biSize = Marshal.SizeOf(bmiDst.bmiHeader)
      .biWidth = 1728
      If y_Res = 196 Then : .biHeight = 2339 : Else : .biHeight = 1169 : End If
      .biPlanes = 1
      .biBitCount = 1
      .biCompression = BI_RGB
    End With
    With bmiDst.bmiColors1
      .rgbBlue = 255
      .rgbGreen = 255
      .rgbRed = 255
    End With
    hbmDst = CreateDIBSection(IntPtr.Zero, bmiDst, DIB_RGB_COLORS, pBits, IntPtr.Zero, 0)

    'сначала создаем DC принтера (с заданным A4+Портрет+разрешение)
    Dim hdcDst1 As IntPtr = CreateDC(vbNullString, FaxPrinter, vbNullString, dm)
    'из него делаем CompatibleDC, куда перенаправим вывод FaxPrintCoverPage
    hdcDst = CreateCompatibleDC(hdcDst1)
    DeleteDC(hdcDst1) 'это можно сразу грохнуть
    hbmOldDst = SelectObject(hdcDst, hbmDst)

    PatBlt(hdcDst, 0, 0, bmiDst.bmiHeader.biWidth, bmiDst.bmiHeader.biHeight, WHITENESS) 'красим белым т.к. он гад черный

    '======распечатка Cover Page=======
    ' FaxContextInfo is the fax context
    Dim FaxContextInfo As New FAX_CONTEXT_INFO
    FaxContextInfo.SizeOfStruct = Marshal.SizeOf(FaxContextInfo)
    FaxContextInfo.hDC = hdcDst 'подсовываем свой hdc в FaxPrintCoverPage

    ' CoverPageInfo is the cover page info
    Dim CoverPageInfo As New FAX_COVERPAGE_INFO
    With CoverPageInfo
      .SizeOfStruct = Marshal.SizeOf(CoverPageInfo)
      '
      .UseServerCoverPage = UseServerCoverPage
      .CoverPageName = CoverPageName ' "confident" ' "C:\ProgramData\Microsoft\Windows NT\MSFax\Common Coverpages\ru-RU\confident.cov"
      'recipient
      .RecName = RecName
      .RecFaxNumber = RecFaxNumber
      'sender
      .SdrName = SdrName
      .SdrFaxNumber = SdrFaxNumber
      .SdrCompany = SdrCompany
      .SdrDepartment = SdrDepartment
      .SdrTitle = SdrTitle
      .SdrOfficeLocation = SdrOfficeLocation
      .SdrHomePhone = SdrHomePhone
      .SdrOfficePhone = SdrOfficePhone
      .SdrAddress = SdrAddress
      '
      .Subject = Subject
      .Note = Note
      .PageCount = FrameCount + 1
    End With

    Try
      'печать титульной страницы (FaxContextInfo.hDC = hdcDst)
      If FaxPrintCoverPage(FaxContextInfo, CoverPageInfo) = False Then
        bCreateCover = False
        'Debug.Print("FaxPrintCoverPage Error " & RaiseAPIError())
      End If
    Catch 'на случай если MSFax не установлен!
      bCreateCover = False
      'Debug.Print(Err.Description) 'если winfax.dll не найдена
    End Try

    SelectObject(hdcDst, hbmOldDst) : hbmOldDst = IntPtr.Zero
    DeleteDC(hdcDst) : hdcDst = IntPtr.Zero


    SelectObject(hdcDst, hbmOldDst) : hbmOldDst = IntPtr.Zero
    DeleteDC(hdcDst) : hdcDst = IntPtr.Zero

    If bCreateCover = True Then
      'сохранение в m_CoverBitmap титульной страницы
      m_CoverBitmap = Bitmap.FromHbitmap(hbmDst)
      m_CoverBitmap.SetResolution(204, y_Res)
    End If

    DeleteObject(hbmDst) : hbmDst = IntPtr.Zero : pBits = IntPtr.Zero
  End Sub


3. А вот дальше надо срастить m_CoverBitmap + m_Bitmap, для каноничности в той же "compression", что и исходный файл.

И здесь как бы код вполне красивый и простой работает прилично и быстро, в отличии от тех наворотов, которые у меня были изначально:
  Public Function SaveMultiFrameAs1bppTIFFWithCoverPage( _
   ByVal FileName As String, _
   Optional ByVal startFrame As Integer = 0, Optional ByVal endFrame As Integer = 0, _
   Optional ByVal eTifCompression As EncoderValue = EncoderValue.CompressionNone) As Boolean

    SaveMultiFrameAs1bppTIFFWithCoverPage = True

    Dim startF As Integer
    Dim endF As Integer
    If (startFrame = 0) Or (startFrame > FrameCount) Then
      startF = 1
    Else
      startF = startFrame
    End If
    If (endFrame = 0) Or (endFrame > FrameCount) Then
      endF = FrameCount
    ElseIf (endFrame < startF) Then
      endF = startF
    Else
      endF = endFrame
    End If

    ' Get an ImageCodecInfo object that represents the TIFF codec.
    Dim myImageCodecInfo As ImageCodecInfo = GetEncoderInfo("image/tiff")
    ' Create an EncoderParameters object. An EncoderParameters object has an array of EncoderParameter 
    ' objects. In this case, there are 2 EncoderParameter objects in the array.
    Dim myEncoderParameters As EncoderParameters = New EncoderParameters(2)
    myEncoderParameters.Param(0) = New EncoderParameter(Encoder.Compression, Fix(eTifCompression))

    '========Cover Page========

    If HasCover Then
      'сохранение в TIFF титульной страницы
      Try
        ' save first image in TIFF
        myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.MultiFrame))
        m_CoverBitmap.Save(FileName, myImageCodecInfo, myEncoderParameters)
      Catch
      End Try
    Else
      Return False 'при отсутствии CoverPage ничего не сохраняем и возвращаем False
    End If
    '========Cover Page========

    Dim nActiveFrame As Integer = frameIndex 'запомним, чтоб потом вернуть
    For i As Integer = startF To endF
      frameIndex = i ' m_Bitmap.SelectActiveFrame(FrameDimension.Page, i - 1)
      Try
        myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.FrameDimensionPage))
        m_CoverBitmap.SaveAdd(m_Bitmap, myEncoderParameters)
      Catch
      End Try
    Next i
    frameIndex = nActiveFrame 'устанавливаем "текущий" frame == m_Bitmap.SelectActiveFrame(FrameDimension.Page, nActiveFrame - 1)

    ' закрываем многокадровый файл
    myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.Flush))
    Try : m_CoverBitmap.SaveAdd(myEncoderParameters) : Catch : End Try
  End Function

Все четко-быстро. На выходе желаемый гибрид в нужном разрешении и компрессии.
Понятно что два TIFF я аналогично сращу подобным же кодом двумя циклами (ну то что те кого сращивают должны быть совместимых разрешений/размеров это понятно на совести кодописателя).
Здесь главное не плодить новых битмапов из одного в другой (можно нарваться что разрешение и палитра скаканут если не думая), и не плодить всяких DrawImage/BitBlt - можно нарваться на непотребное время работы кода.

Ну OK вроде.
30 авг 18, 05:34    [21658322]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Изопропил
Member

Откуда:
Сообщений: 31113
Дмитрий77,

Ты же хочешь чтобы быстро было и без ковыряний в подробностях

Для этого достатчно осилить несколько страниц описвния формата,
причём не вникая в семантику
30 авг 18, 08:30    [21658373]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Изопропил
Ты же хочешь чтобы быстро было и без ковыряний в подробностях

Дык уже получилось быстро и считай без особых ковыряний.
Код крайний мой смотрел внизу? Где там ковыряния? Просто грузишь tif-ы в битмапы (тупо одной строкой), и последовательно бухаешь все фреймы в новый файл, проходясь по битмапам циклом .SelectActiveFrame(FrameDimension.Page, i - 1).
Проверил нарочно на более слабом компе, все равно быстро.
cover+52 страницы, меньше секунды, это с учетом таки "ковыряния" с cover.

Изопропил
Для этого достатчно осилить несколько страниц описвния формата,

А вот это уже ковыряния будут.
30 авг 18, 23:03    [21659496]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
склеить

Если просто склеить, вот накатал ф-цию, м.б. кому пригодится.
!!! заведомо известно, что разрешения одинаковы иначе ИМХО фигня будет. (и в зависимости от задачи м.б. чего еще, для меня размеры критичны)
Т.е. оно не анализирует "форматы" склеиваемых файлов.

Imports System.Drawing.Imaging

Module m_TIFF

  ''' <summary>
  ''' This function will join the TIFF file with a specific compression format
  ''' </summary>
  ''' <param name="imageFiles">string array with source image files</param>
  ''' <param name="outFile">target TIFF file to be produced</param>
  ''' <param name="eTifCompression">compression codec enum</param>
  Public Function JoinTiffImages(ByVal imageFiles As String(), ByVal outFile As String, _
   Optional ByVal eTifCompression As EncoderValue = EncoderValue.CompressionNone, _
   Optional ByRef err_text As String = vbNullString) As Boolean

    Dim m_BaseBitmap As Bitmap = Nothing

    Try
      'If only one file in the collection, copy it directly to the target file.
      If UBound(imageFiles) = 0 Then
        IO.File.Copy(imageFiles(0), outFile, True)
        Return True
      End If

      ' Get an ImageCodecInfo object that represents the TIFF codec.
      Dim myImageCodecInfo As ImageCodecInfo = GetEncoderInfo("image/tiff")
      ' Create an EncoderParameters object. An EncoderParameters object has an array of EncoderParameter 
      ' objects. In this case, there are 2 EncoderParameter objects in the array.
      Dim myEncoderParameters As EncoderParameters = New EncoderParameters(2)
      myEncoderParameters.Param(0) = New EncoderParameter(Encoder.Compression, Fix(eTifCompression))

      For i As Integer = 0 To UBound(imageFiles)
        If i = 0 Then
          m_BaseBitmap = New Bitmap(imageFiles(0))
          Dim m_frameCount As Integer = m_BaseBitmap.GetFrameCount(FrameDimension.Page)
          ' save first page of the first file in TIFF
          m_BaseBitmap.SelectActiveFrame(FrameDimension.Page, 0)
          myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.MultiFrame))
          m_BaseBitmap.Save(outFile, myImageCodecInfo, myEncoderParameters)
          If m_frameCount > 1 Then ' save other pages of the first file
            For j As Integer = 1 To m_frameCount - 1
              m_BaseBitmap.SelectActiveFrame(FrameDimension.Page, j)
              myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.FrameDimensionPage))
              m_BaseBitmap.SaveAdd(m_BaseBitmap, myEncoderParameters)
            Next j
          End If
        Else
          Dim m_Bitmap As Bitmap = New Bitmap(imageFiles(i))
          Dim m_frameCount As Integer = m_Bitmap.GetFrameCount(FrameDimension.Page)
          For j As Integer = 0 To m_frameCount - 1
            m_Bitmap.SelectActiveFrame(FrameDimension.Page, j)
            myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.FrameDimensionPage))
            m_BaseBitmap.SaveAdd(m_Bitmap, myEncoderParameters)
          Next j
          m_Bitmap.Dispose() : m_Bitmap = Nothing
        End If
      Next i

      ' закрываем многокадровый файл
      myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.Flush))
      m_BaseBitmap.SaveAdd(myEncoderParameters)
      m_BaseBitmap.Dispose() : m_BaseBitmap = Nothing
      Return True
    Catch
      err_text = Err.Number.ToString & " (" & Err.Description & ")"
      Try : m_BaseBitmap.Dispose() : m_BaseBitmap = Nothing : Catch : End Try
      Try : IO.File.Delete(outFile) : Catch : End Try
      Return False
    End Try
  End Function

  Private Function GetEncoderInfo(ByVal mimeType As String) As ImageCodecInfo
    'https://msdn.microsoft.com/en-us/library/system.drawing.imaging.encoder.compression(v=vs.110).aspx

    Dim encoders() As ImageCodecInfo = ImageCodecInfo.GetImageEncoders()
    For Each enc As ImageCodecInfo In encoders
      If enc.MimeType = mimeType Then Return enc
    Next
    Return Nothing
  End Function

End Module
22 сен 18, 05:15    [21682540]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Дмитрий77
склеить

Если просто склеить, вот накатал ф-цию, ...

Мелкий непонятный глюк обнаружил..
Клею 4 файла кодом что полностью привел выше
file0 (1 page)
file1 (1 page)
file2 (1 page)
file3 (2 pages)
На выходе получаю TIFF (5 pages):
file0(1 page)-file1(1 page)-file2(1 page)-file3(2 pages)
Вполне таки съедобный, т.е. кушается факс программой в правильной последовательности страниц file0-file1-file2-file3, виндовая программа также показывает страницы в этой же заказанной последовательности file0-file1-file2-file3.

А если применить к этому склеенному TIFF утилиту tiff2pdf (из libtiff),
то в PDF последние 2 блока переставлены.
file0(1 page)-file1(1 page)-file3(2 pages)-file2(1 page)

Как такое может быть?
Если смотреть код выше
1) Самую первую страницу сохраняю с EncoderValue.MultiFrame
2) Все остальное добавляю с EncoderValue.FrameDimensionPage в заказанной последовательности страниц
3) В конце делаю EncoderValue.Flush

Ну, факс-прога и просмотрщик виндов очевидно смотрят на физическую последовательность страниц
А вот tiff2pdf видимо смотрит в какую-то "таблицу".
В моем примере
file0,file1,file3 финализированы через GDI (винды)
file2 - сделан из SFF утилитой sff2tiff, возможно содержит признак последней страницы, не знаю
Подозреваю что tiff2pdf это как-то отсекает, из за чего пихает его в конец.

Смотрел, есть еще флаг
EncoderValue.LastFrame - Указывает последний кадр многокадрового изображения. Может передаваться кодировщику TIFF в качестве параметра категории флага сохранения.
Я его никак в коде не использую, м.б. воткнуть, только куда?
(копать на низком уровне ес-нно желания нет)
24 сен 18, 20:27    [21684611]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
В общем с ходу не могу понять.
EncoderValue.LastFrame - если вставить в конец ничего не дает.
Если клеить скажем несколько многостраничных, сам склеенный tiff типа нормальный,

но конвертишь его потом в pdf через tiff2pdf,
может сначала сохранить первые страницы из вклеек, потом вторые и т.п. и какую-нибудь серединную вклейку скинуть в конец pdf-а.
Т.е. виндовский gdi-encoder чего-то (по tiff-науке) не дописывает в БЫСТРО склеенный файл,
а libtiff (tiff2pdf) в итоге трактует последовательность страниц по своему.
Чего в быстром коде склейки добавить-поменять, у меня идей нет.

Если делать с пересозданием и полной перерисовкой битмапов, как в моем верхнем посте, то глюков надо думать не будет, но это будет очень медленно.
24 сен 18, 23:35    [21684782]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Изопропил
Member

Откуда:
Сообщений: 31113
Дмитрий77
копать на низком уровне ес-нно желания нет

И что в этом естественного?
Дмитрий77
Чего в быстром коде склейки добавить-поменять, у меня идей нет

Для начала ознакомиться с описанием формата


Хотя «что тут думать, трясти нужно»
25 сен 18, 00:38    [21684810]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Изопропил
Хотя «что тут думать, трясти нужно»

Пытаюсь.
Смотрю что показывает утилита tiffinfo.exe (libtiff).
Клею 1.tif + 2.tif своей быстрой JoinTiffImages

1.tif (2 страницы)
TIFF Directory at offset 0x2d2d0 (185040)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  FillOrder: lsb-to-msb
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 0-2
  Group 3 Options: EOL padding (4 = 0x4)
  1 Strips:
      0: [     100,   109407]
TIFF Directory at offset 0x2d3a6 (185254)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  FillOrder: lsb-to-msb
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 1-2
  Group 3 Options: EOL padding (4 = 0x4)
  1 Strips:
      0: [  109507,    75533]

2.tif (2 страницы)
TIFF Directory at offset 0x1bf62 (114530)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  FillOrder: lsb-to-msb
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 0-2
  Group 3 Options: EOL padding (4 = 0x4)
  1 Strips:
      0: [     100,    59223]
TIFF Directory at offset 0x1c038 (114744)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  FillOrder: lsb-to-msb
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 1-2
  Group 3 Options: EOL padding (4 = 0x4)
  1 Strips:
      0: [   59323,    55207]

После склейки имеем вот это:
final.tif = 1.tif + 2.tif
TIFF Directory at offset 0x1a7a4 (108452)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 0-2
  1 Strips:
      0: [       8,   108444]
TIFF Directory at offset 0x2cbbe (183230)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 1-2
  1 Strips:
      0: [  108642,    74588]
TIFF Directory at offset 0x3b02c (241708)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1
  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 0-2
  1 Strips:
      0: [  183420,    58287]
TIFF Directory at offset 0x484e2 (296162)
  Subfile Type: (0 = 0x0)
  Image Width: 1728 Image Length: 2340
  Resolution: 204, 196 pixels/inch
  Bits/Sample: 1

  Compression Scheme: CCITT Group 3
  Photometric Interpretation: min-is-white
  Samples/Pixel: 1
  Rows/Strip: 2340
  Planar Configuration: single image plane
  Page Number: 1-2
  1 Strips:
      0: [  241898,    54264]

Файл типа рабочий (если не копать, а последовательно читать картинки), но подвох в нумерации страниц, он их не перенумеровал
0-4
1-4
2-4
3-4
а оставил как есть
0-2
1-2
0-2
1-2
Виндовский вьюер - читает правильно.
При попытке сделать этому файлу tiff2pdf имеем неправильную последовательность:
1(стр.1)-2(стр.1)-1(стр.2)-2(стр.2) (умничает, но тасует)
Вьюер от вентафакс показывает только первые две страницы.
Кто во что горазд (типа кто как начитался теории, так и интерпретирует).

Спрашивается, почему моя ф-ция JoinTiffImages
m_BaseBitmap.Save
m_BaseBitmap.SaveAdd
...
не проставляет новые номера страниц при создании нового tiff.
GDI+ же не настолько тупое, значит я что-то упустил или как так?
25 сен 18, 01:30    [21684823]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
не проставляет новые номера страниц при создании нового tiff.

Вроде нащупал куда копать.
PropertyTagPageNumber
    PropertyTagPageNumber = &H129

Задается для каждой страницы каждого bitmap
Прочесть можно типа так
              m_BaseBitmap.SelectActiveFrame(FrameDimension.Page, j)
              Dim propItem1 As PropertyItem = m_BaseBitmap.GetPropertyItem(Image_Property_ID_Tags.PropertyTagPageNumber)
              Dim sItem1 As String = ""
              For k As Integer = 1 To PropertyItem_ValueCount(propItem1)
                If k > 1 Then sItem1 = sItem1 & ", "
                sItem1 = sItem1 & PropertyItem_ParseShort(propItem1, k).ToString
              Next
              MsgBox(sItem1)

В моем примере выдаст
0,2
1,2
0,2
1,2
Код склеивания (быстрого) эти таги не перезаписывает. Отсюда бардак.
Как простой вариант, можно просто их поудалять.
 m_BaseBitmap.SelectActiveFrame(FrameDimension.Page, j)
 m_BaseBitmap.RemovePropertyItem(Image_Property_ID_Tags.PropertyTagPageNumber)

(ничего лучше чем путаница),
а далее зависит от вольной интерпритации проги, что будет работать с этим tiff.
Ну т.е. tiff2pdf уже будет в правильной последовательности страниц,
а вот какая-нибудь вента вообще этот тиф не прочитает.

Поэтому видно придется попариться и прописать таки эти таги "ручками" в каждый фрейм чтоб аккуратно было.
25 сен 18, 03:25    [21684855]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Все, осилил, теперь без глюков.
Пришлось изуродовать ф-цию и вычислять-пихать SetPropertyItem(<Image_Property_ID_Tags.PropertyTagPageNumber>) в каждую страницу.
Но это жесть какая-то:
Image.SetPropertyItem(PropertyItem) Method
It is difficult to set property items, because the PropertyItem class has no public constructors. One way to work around this restriction is to obtain a PropertyItem by retrieving the PropertyItems property value or calling the GetPropertyItem method of an Image that already has property items. Then you can set the fields of the PropertyItem and pass it to SetPropertyItem.


А вообще фигня, что эта Encoder-кухня сама автоматом страницы не нумерует при записи файла.
Либо я чего-то недоглядел, недочитал.

Imports System.Drawing.Imaging

Module m_TIFF
  ''' <summary>
  ''' This function will join the TIFF file with a specific compression format
  ''' </summary>
  ''' <param name="imageFiles">string array with source image files</param>
  ''' <param name="outFile">target TIFF file to be produced</param>
  ''' <param name="eTifCompression">compression codec enum</param>
  Public Function JoinTiffImages(ByVal imageFiles As String(), ByVal outFile As String, _
   Optional ByVal eTifCompression As EncoderValue = EncoderValue.CompressionNone, _
   Optional ByRef err_text As String = vbNullString) As Boolean

    Dim m_BaseBitmap As Bitmap = Nothing
    Dim m_Bitmap() As Bitmap
    Dim pagesTotal As Integer
    Dim curPage As Short = 0

    Try
      'If only one file in the collection, copy it directly to the target file.
      If UBound(imageFiles) = 0 Then
        IO.File.Copy(imageFiles(0), outFile, True)
        Return True
      End If

      'Надо б сразу вычислить общее к-во страниц
      m_BaseBitmap = New Bitmap(imageFiles(0))
      pagesTotal = m_BaseBitmap.GetFrameCount(FrameDimension.Page)
      ReDim m_Bitmap(0 To UBound(imageFiles)) 'нулевой элемент не используется
      For i As Integer = 1 To UBound(imageFiles)
        m_Bitmap(i) = New Bitmap(imageFiles(i))
        pagesTotal = pagesTotal + m_Bitmap(i).GetFrameCount(FrameDimension.Page)
      Next

      ' Get an ImageCodecInfo object that represents the TIFF codec.
      Dim myImageCodecInfo As ImageCodecInfo = GetEncoderInfo("image/tiff")
      ' Create an EncoderParameters object. An EncoderParameters object has an array of EncoderParameter 
      ' objects. In this case, there are 2 EncoderParameter objects in the array.
      Dim myEncoderParameters As EncoderParameters = New EncoderParameters(2)
      myEncoderParameters.Param(0) = New EncoderParameter(Encoder.Compression, Fix(eTifCompression))

      For i As Integer = 0 To UBound(imageFiles)
        If i = 0 Then
          Dim m_frameCount As Integer = m_BaseBitmap.GetFrameCount(FrameDimension.Page)
          ' save first page of the first file in TIFF
          m_BaseBitmap.SelectActiveFrame(FrameDimension.Page, 0)
          m_BaseBitmap.SetPropertyItem(CreatePropItemPageNumber(m_BaseBitmap, curPage, CType(pagesTotal, Short)))
          curPage = curPage + 1
          myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.MultiFrame))
          m_BaseBitmap.Save(outFile, myImageCodecInfo, myEncoderParameters)
          If m_frameCount > 1 Then ' save other pages of the first file
            For j As Integer = 1 To m_frameCount - 1
              m_BaseBitmap.SelectActiveFrame(FrameDimension.Page, j)
              m_BaseBitmap.SetPropertyItem(CreatePropItemPageNumber(m_BaseBitmap, curPage, CType(pagesTotal, Short)))
              curPage = curPage + 1
              myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.FrameDimensionPage))
              m_BaseBitmap.SaveAdd(m_BaseBitmap, myEncoderParameters)
            Next j
          End If
        Else
          Dim m_frameCount As Integer = m_Bitmap(i).GetFrameCount(FrameDimension.Page)
          For j As Integer = 0 To m_frameCount - 1
            m_Bitmap(i).SelectActiveFrame(FrameDimension.Page, j)
            m_Bitmap(i).SetPropertyItem(CreatePropItemPageNumber(m_Bitmap(i), curPage, CType(pagesTotal, Short)))
            curPage = curPage + 1
            myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.FrameDimensionPage))
            m_BaseBitmap.SaveAdd(m_Bitmap(i), myEncoderParameters)
          Next j
          m_Bitmap(i).Dispose() : m_Bitmap(i) = Nothing
        End If
      Next i

      ' закрываем многокадровый файл
      myEncoderParameters.Param(1) = New EncoderParameter(Encoder.SaveFlag, Fix(EncoderValue.Flush))
      m_BaseBitmap.SaveAdd(myEncoderParameters)
      m_BaseBitmap.Dispose() : m_BaseBitmap = Nothing
      Return True
    Catch
      err_text = Err.Number.ToString & " (" & Err.Description & ")"
      Try : m_BaseBitmap.Dispose() : m_BaseBitmap = Nothing : Catch : End Try
      Try : IO.File.Delete(outFile) : Catch : End Try
      Return False
    End Try
  End Function

  Private Function CreatePropItemPageNumber(ByRef m_bitmap As Bitmap, _
   ByVal Page As Short, ByVal PagesTotal As Short) As PropertyItem
    'Workaround из-за отсутствия конструктора New() -капец какой-то
    'https://docs.microsoft.com/en-us/dotnet/api/system.drawing.image.setpropertyitem?view=netframework-4.7.2
    'It is difficult to set property items, because the PropertyItem class has no public constructors.
    'One way to work around this restriction is to obtain a PropertyItem by retrieving the PropertyItems
    'property value or calling the GetPropertyItem method of an Image that already has property items
    'Пофиг какой PropertyItem брать, берем первый из всех (хоть один таг то точно есть)
    Dim propItem As PropertyItem = m_bitmap.PropertyItems(0)
    With propItem
      propItem.Id = Image_Property_ID_Tags.PropertyTagPageNumber
      propItem.Type = PropertyTagType.PropertyTagTypeShort
      propItem.Len = 4 'два Short пакуется в 4 байта
      Dim aa() As Byte = BitConverter.GetBytes(Page) 'номер страницы 0-based
      Dim bb() As Byte = BitConverter.GetBytes(PagesTotal) 'всего страниц
      propItem.Value = {aa(0), aa(1), bb(0), bb(1)}
    End With
    Return propItem
  End Function

  Private Function GetEncoderInfo(ByVal mimeType As String) As ImageCodecInfo
    'https://msdn.microsoft.com/en-us/library/system.drawing.imaging.encoder.compression(v=vs.110).aspx

    Dim encoders() As ImageCodecInfo = ImageCodecInfo.GetImageEncoders()
    For Each enc As ImageCodecInfo In encoders
      If enc.MimeType = mimeType Then Return enc
    Next
    Return Nothing
  End Function

End Module
25 сен 18, 06:45    [21684891]     Ответить | Цитировать Сообщить модератору
 Re: А есть способ БЫСТРО запихнуть еще одну страницу в начало многостраничного TIFF  [new]
Дмитрий77
Member

Откуда:
Сообщений: 4341
Нет, но вообще ерунда какая-то.
Посмотрел тут еще ряд старых своих кодов, где я просто сохраняю что-то в TIFF через GetEncoderInfo("image/tiff").
Не пишет он сволочь PropertyTagPageNumber, хоть по пикселям рисуй, хоть тупо клей.

Плюс
Но это жесть какая-то:
Image.SetPropertyItem(PropertyItem) Method
It is difficult to set property items, because the PropertyItem class has no public constructors. One way to work around this restriction is to obtain a PropertyItem by retrieving the PropertyItems property value or calling the GetPropertyItem method of an Image that already has property items. Then you can set the fields of the PropertyItem and pass it to SetPropertyItem.

До кучи вот нашел еще на эту тему:
Назначение свойств изображению

Т.е. получается и "ручками" этот PropertyTagPageNumber и не всегда вставишь (в общем случае).
Если я гружу m_bitmap из файла,
m_Bitmap = New Bitmap(imageFiles)
OK, я получаю объект PropertyItem (хоть какой-нибудь, чтоб его потом мудифицировать) как m_bitmap.PropertyItems(0).

А вот если я этот m_Bitmap создаю с нуля, чтоб в него рисовать, а потом сохранить (ну например распечатка чистой Cover Page на hdc как приводил пример), то мне и взять-то PropertyItem в общем случае (без извращений) неоткуда, потому как в m_bitmap.PropertyItems(0) будет тупо пусто (до того как во что-то сохранил).

Я вот думаю, надо ли продолжать копать или просто забить (для случаев когда просто сохраняю одну страницу TIFF или даже несколько страниц с нуля).
Если одна страница или несколько без PropertyTagPageNumber в страницах, то большинство софта (включая мой) будут с этим TIFF корректно работать (тупое последовательное чтение картинок).
Бардак возникает только при склеивании, когда в тагах получается мешанина: то бишь где-то страницы указаны, где-то нет, где-то повторяются, где-то некорректно указано общее к-во страниц, но эту основную проблему вроде как порешил.
25 сен 18, 18:11    [21685812]     Ответить | Цитировать Сообщить модератору
Все форумы / WinForms, .Net Framework Ответить