winegstreamer: Flip video output.
Gstreamer handles video top-down, but Windows's dshow handles it bottom-up. So let's insert a videoflip filter to fix the discrepancy instead of relying on videorenderer to flip it like we did before. Signed-off-by: Andrew Eikum <aeikum@codeweavers.com> Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
parent
008e4d3e86
commit
6107b36e56
|
@ -64,6 +64,7 @@ typedef struct GSTImpl {
|
||||||
LONGLONG filesize;
|
LONGLONG filesize;
|
||||||
|
|
||||||
BOOL discont, initial, ignore_flush;
|
BOOL discont, initial, ignore_flush;
|
||||||
|
GstElement *container;
|
||||||
GstElement *gstfilter;
|
GstElement *gstfilter;
|
||||||
GstPad *my_src, *their_sink;
|
GstPad *my_src, *their_sink;
|
||||||
GstBus *bus;
|
GstBus *bus;
|
||||||
|
@ -78,6 +79,8 @@ struct GSTOutPin {
|
||||||
BaseOutputPin pin;
|
BaseOutputPin pin;
|
||||||
IQualityControl IQualityControl_iface;
|
IQualityControl IQualityControl_iface;
|
||||||
|
|
||||||
|
GstElement *flipfilter;
|
||||||
|
GstPad *flip_sink, *flip_src;
|
||||||
GstPad *their_src;
|
GstPad *their_src;
|
||||||
GstPad *my_sink;
|
GstPad *my_sink;
|
||||||
GstBufferPool *gstpool;
|
GstBufferPool *gstpool;
|
||||||
|
@ -751,8 +754,14 @@ static void removed_decoded_pad(GstElement *bin, GstPad *pad, gpointer user)
|
||||||
}
|
}
|
||||||
if (x == This->cStreams)
|
if (x == This->cStreams)
|
||||||
goto out;
|
goto out;
|
||||||
|
|
||||||
pin = This->ppPins[x];
|
pin = This->ppPins[x];
|
||||||
|
|
||||||
|
if(pin->flipfilter)
|
||||||
|
gst_pad_unlink(pin->their_src, pin->flip_sink);
|
||||||
|
else
|
||||||
gst_pad_unlink(pin->their_src, pin->my_sink);
|
gst_pad_unlink(pin->their_src, pin->my_sink);
|
||||||
|
|
||||||
gst_object_unref(pin->their_src);
|
gst_object_unref(pin->their_src);
|
||||||
pin->their_src = NULL;
|
pin->their_src = NULL;
|
||||||
out:
|
out:
|
||||||
|
@ -819,10 +828,72 @@ static void init_new_decoded_pad(GstElement *bin, GstPad *pad, GSTImpl *This)
|
||||||
pin->isvid = isvid;
|
pin->isvid = isvid;
|
||||||
|
|
||||||
gst_segment_init(pin->segment, GST_FORMAT_TIME);
|
gst_segment_init(pin->segment, GST_FORMAT_TIME);
|
||||||
|
|
||||||
|
if (isvid) {
|
||||||
|
TRACE("setting up videoflip filter for pin %p, my_sink: %p, their_src: %p\n",
|
||||||
|
pin, pin->my_sink, pad);
|
||||||
|
|
||||||
|
/* gstreamer outputs video top-down, but dshow expects bottom-up, so
|
||||||
|
* make new transform filter to invert video */
|
||||||
|
pin->flipfilter = gst_element_factory_make("videoflip", NULL);
|
||||||
|
if(!pin->flipfilter){
|
||||||
|
ERR("Missing videoflip filter?\n");
|
||||||
|
ret = -1;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
gst_util_set_object_arg(G_OBJECT(pin->flipfilter), "method", "vertical-flip");
|
||||||
|
|
||||||
|
gst_bin_add(GST_BIN(This->container), pin->flipfilter);
|
||||||
|
gst_element_sync_state_with_parent(pin->flipfilter);
|
||||||
|
|
||||||
|
pin->flip_sink = gst_element_get_static_pad(pin->flipfilter, "sink");
|
||||||
|
if(!pin->flip_sink){
|
||||||
|
WARN("Couldn't find sink on flip filter\n");
|
||||||
|
gst_object_unref(pin->flipfilter);
|
||||||
|
pin->flipfilter = NULL;
|
||||||
|
ret = -1;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = gst_pad_link(pad, pin->flip_sink);
|
||||||
|
if(ret < 0){
|
||||||
|
WARN("gst_pad_link failed: %d\n", ret);
|
||||||
|
gst_object_unref(pin->flip_sink);
|
||||||
|
pin->flip_sink = NULL;
|
||||||
|
gst_object_unref(pin->flipfilter);
|
||||||
|
pin->flipfilter = NULL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
pin->flip_src = gst_element_get_static_pad(pin->flipfilter, "src");
|
||||||
|
if(!pin->flip_src){
|
||||||
|
WARN("Couldn't find src on flip filter\n");
|
||||||
|
gst_object_unref(pin->flip_sink);
|
||||||
|
pin->flip_sink = NULL;
|
||||||
|
gst_object_unref(pin->flipfilter);
|
||||||
|
pin->flipfilter = NULL;
|
||||||
|
ret = -1;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = gst_pad_link(pin->flip_src, pin->my_sink);
|
||||||
|
if(ret < 0){
|
||||||
|
WARN("gst_pad_link failed: %d\n", ret);
|
||||||
|
gst_object_unref(pin->flip_src);
|
||||||
|
pin->flip_src = NULL;
|
||||||
|
gst_object_unref(pin->flip_sink);
|
||||||
|
pin->flip_sink = NULL;
|
||||||
|
gst_object_unref(pin->flipfilter);
|
||||||
|
pin->flipfilter = NULL;
|
||||||
|
goto exit;
|
||||||
|
}
|
||||||
|
} else
|
||||||
ret = gst_pad_link(pad, mypad);
|
ret = gst_pad_link(pad, mypad);
|
||||||
|
|
||||||
gst_pad_set_active(mypad, 1);
|
gst_pad_set_active(mypad, 1);
|
||||||
|
|
||||||
|
exit:
|
||||||
TRACE("Linking: %i\n", ret);
|
TRACE("Linking: %i\n", ret);
|
||||||
|
|
||||||
if (ret >= 0) {
|
if (ret >= 0) {
|
||||||
|
@ -834,7 +905,7 @@ static void init_new_decoded_pad(GstElement *bin, GstPad *pad, GSTImpl *This)
|
||||||
static void existing_new_pad(GstElement *bin, GstPad *pad, gpointer user)
|
static void existing_new_pad(GstElement *bin, GstPad *pad, gpointer user)
|
||||||
{
|
{
|
||||||
GSTImpl *This = (GSTImpl*)user;
|
GSTImpl *This = (GSTImpl*)user;
|
||||||
int x;
|
int x, ret;
|
||||||
|
|
||||||
TRACE("%p %p %p\n", This, bin, pad);
|
TRACE("%p %p %p\n", This, bin, pad);
|
||||||
|
|
||||||
|
@ -852,7 +923,13 @@ static void existing_new_pad(GstElement *bin, GstPad *pad, gpointer user)
|
||||||
GSTOutPin *pin = This->ppPins[x];
|
GSTOutPin *pin = This->ppPins[x];
|
||||||
if (!pin->their_src) {
|
if (!pin->their_src) {
|
||||||
gst_segment_init(pin->segment, GST_FORMAT_TIME);
|
gst_segment_init(pin->segment, GST_FORMAT_TIME);
|
||||||
if (gst_pad_link(pad, pin->my_sink) >= 0) {
|
|
||||||
|
if (pin->flipfilter)
|
||||||
|
ret = gst_pad_link(pad, pin->flip_sink);
|
||||||
|
else
|
||||||
|
ret = gst_pad_link(pad, pin->my_sink);
|
||||||
|
|
||||||
|
if (ret >= 0) {
|
||||||
pin->their_src = pad;
|
pin->their_src = pad;
|
||||||
gst_object_ref(pin->their_src);
|
gst_object_ref(pin->their_src);
|
||||||
TRACE("Relinked\n");
|
TRACE("Relinked\n");
|
||||||
|
@ -1023,12 +1100,17 @@ static HRESULT GST_Connect(GSTInPin *pPin, IPin *pConnectPin, ALLOCATOR_PROPERTI
|
||||||
gst_bus_set_sync_handler(This->bus, watch_bus_wrapper, This, NULL);
|
gst_bus_set_sync_handler(This->bus, watch_bus_wrapper, This, NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
This->container = gst_bin_new(NULL);
|
||||||
|
|
||||||
This->gstfilter = gst_element_factory_make("decodebin", NULL);
|
This->gstfilter = gst_element_factory_make("decodebin", NULL);
|
||||||
if (!This->gstfilter) {
|
if (!This->gstfilter) {
|
||||||
FIXME("Could not make source filter, are gstreamer-plugins-* installed for %u bits?\n",
|
FIXME("Could not make source filter, are gstreamer-plugins-* installed for %u bits?\n",
|
||||||
8 * (int)sizeof(void*));
|
8 * (int)sizeof(void*));
|
||||||
return E_FAIL;
|
return E_FAIL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
gst_bin_add(GST_BIN(This->container), This->gstfilter);
|
||||||
|
|
||||||
gst_element_set_bus(This->gstfilter, This->bus);
|
gst_element_set_bus(This->gstfilter, This->bus);
|
||||||
g_signal_connect(This->gstfilter, "pad-added", G_CALLBACK(existing_new_pad_wrapper), This);
|
g_signal_connect(This->gstfilter, "pad-added", G_CALLBACK(existing_new_pad_wrapper), This);
|
||||||
g_signal_connect(This->gstfilter, "pad-removed", G_CALLBACK(removed_decoded_pad_wrapper), This);
|
g_signal_connect(This->gstfilter, "pad-removed", G_CALLBACK(removed_decoded_pad_wrapper), This);
|
||||||
|
@ -1054,9 +1136,9 @@ static HRESULT GST_Connect(GSTInPin *pPin, IPin *pConnectPin, ALLOCATOR_PROPERTI
|
||||||
/* Add initial pins */
|
/* Add initial pins */
|
||||||
This->initial = This->discont = TRUE;
|
This->initial = This->discont = TRUE;
|
||||||
ResetEvent(This->event);
|
ResetEvent(This->event);
|
||||||
gst_element_set_state(This->gstfilter, GST_STATE_PLAYING);
|
gst_element_set_state(This->container, GST_STATE_PLAYING);
|
||||||
WaitForSingleObject(This->event, -1);
|
WaitForSingleObject(This->event, -1);
|
||||||
gst_element_get_state(This->gstfilter, NULL, NULL, -1);
|
gst_element_get_state(This->container, NULL, NULL, -1);
|
||||||
|
|
||||||
if (ret < 0) {
|
if (ret < 0) {
|
||||||
WARN("Ret: %i\n", ret);
|
WARN("Ret: %i\n", ret);
|
||||||
|
@ -1078,8 +1160,8 @@ static HRESULT GST_Connect(GSTInPin *pPin, IPin *pConnectPin, ALLOCATOR_PROPERTI
|
||||||
*props = This->props;
|
*props = This->props;
|
||||||
|
|
||||||
This->ignore_flush = TRUE;
|
This->ignore_flush = TRUE;
|
||||||
gst_element_set_state(This->gstfilter, GST_STATE_READY);
|
gst_element_set_state(This->container, GST_STATE_READY);
|
||||||
gst_element_get_state(This->gstfilter, NULL, NULL, -1);
|
gst_element_get_state(This->container, NULL, NULL, -1);
|
||||||
This->ignore_flush = FALSE;
|
This->ignore_flush = FALSE;
|
||||||
|
|
||||||
This->initial = FALSE;
|
This->initial = FALSE;
|
||||||
|
@ -1262,10 +1344,10 @@ static HRESULT WINAPI GST_Stop(IBaseFilter *iface)
|
||||||
|
|
||||||
mark_wine_thread();
|
mark_wine_thread();
|
||||||
|
|
||||||
if (This->gstfilter) {
|
if (This->container) {
|
||||||
This->ignore_flush = TRUE;
|
This->ignore_flush = TRUE;
|
||||||
gst_element_set_state(This->gstfilter, GST_STATE_READY);
|
gst_element_set_state(This->container, GST_STATE_READY);
|
||||||
gst_element_get_state(This->gstfilter, NULL, NULL, -1);
|
gst_element_get_state(This->container, NULL, NULL, -1);
|
||||||
This->ignore_flush = FALSE;
|
This->ignore_flush = FALSE;
|
||||||
}
|
}
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
@ -1280,19 +1362,19 @@ static HRESULT WINAPI GST_Pause(IBaseFilter *iface)
|
||||||
|
|
||||||
TRACE("(%p)\n", This);
|
TRACE("(%p)\n", This);
|
||||||
|
|
||||||
if (!This->gstfilter)
|
if (!This->container)
|
||||||
return VFW_E_NOT_CONNECTED;
|
return VFW_E_NOT_CONNECTED;
|
||||||
|
|
||||||
mark_wine_thread();
|
mark_wine_thread();
|
||||||
|
|
||||||
gst_element_get_state(This->gstfilter, &now, NULL, -1);
|
gst_element_get_state(This->container, &now, NULL, -1);
|
||||||
if (now == GST_STATE_PAUSED)
|
if (now == GST_STATE_PAUSED)
|
||||||
return S_OK;
|
return S_OK;
|
||||||
if (now != GST_STATE_PLAYING)
|
if (now != GST_STATE_PLAYING)
|
||||||
hr = IBaseFilter_Run(iface, -1);
|
hr = IBaseFilter_Run(iface, -1);
|
||||||
if (FAILED(hr))
|
if (FAILED(hr))
|
||||||
return hr;
|
return hr;
|
||||||
ret = gst_element_set_state(This->gstfilter, GST_STATE_PAUSED);
|
ret = gst_element_set_state(This->container, GST_STATE_PAUSED);
|
||||||
if (ret == GST_STATE_CHANGE_ASYNC)
|
if (ret == GST_STATE_CHANGE_ASYNC)
|
||||||
hr = S_FALSE;
|
hr = S_FALSE;
|
||||||
return hr;
|
return hr;
|
||||||
|
@ -1310,26 +1392,26 @@ static HRESULT WINAPI GST_Run(IBaseFilter *iface, REFERENCE_TIME tStart)
|
||||||
|
|
||||||
mark_wine_thread();
|
mark_wine_thread();
|
||||||
|
|
||||||
if (!This->gstfilter)
|
if (!This->container)
|
||||||
return VFW_E_NOT_CONNECTED;
|
return VFW_E_NOT_CONNECTED;
|
||||||
|
|
||||||
EnterCriticalSection(&This->filter.csFilter);
|
EnterCriticalSection(&This->filter.csFilter);
|
||||||
This->filter.rtStreamStart = tStart;
|
This->filter.rtStreamStart = tStart;
|
||||||
LeaveCriticalSection(&This->filter.csFilter);
|
LeaveCriticalSection(&This->filter.csFilter);
|
||||||
|
|
||||||
gst_element_get_state(This->gstfilter, &now, NULL, -1);
|
gst_element_get_state(This->container, &now, NULL, -1);
|
||||||
if (now == GST_STATE_PLAYING)
|
if (now == GST_STATE_PLAYING)
|
||||||
return S_OK;
|
return S_OK;
|
||||||
if (now == GST_STATE_PAUSED) {
|
if (now == GST_STATE_PAUSED) {
|
||||||
GstStateChangeReturn ret;
|
GstStateChangeReturn ret;
|
||||||
ret = gst_element_set_state(This->gstfilter, GST_STATE_PLAYING);
|
ret = gst_element_set_state(This->container, GST_STATE_PLAYING);
|
||||||
if (ret == GST_STATE_CHANGE_ASYNC)
|
if (ret == GST_STATE_CHANGE_ASYNC)
|
||||||
return S_FALSE;
|
return S_FALSE;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
EnterCriticalSection(&This->filter.csFilter);
|
EnterCriticalSection(&This->filter.csFilter);
|
||||||
gst_element_set_state(This->gstfilter, GST_STATE_PLAYING);
|
gst_element_set_state(This->container, GST_STATE_PLAYING);
|
||||||
This->filter.rtStreamStart = tStart;
|
This->filter.rtStreamStart = tStart;
|
||||||
|
|
||||||
for (i = 0; i < This->cStreams; i++) {
|
for (i = 0; i < This->cStreams; i++) {
|
||||||
|
@ -1355,12 +1437,12 @@ static HRESULT WINAPI GST_GetState(IBaseFilter *iface, DWORD dwMilliSecsTimeout,
|
||||||
|
|
||||||
mark_wine_thread();
|
mark_wine_thread();
|
||||||
|
|
||||||
if (!This->gstfilter) {
|
if (!This->container) {
|
||||||
*pState = State_Stopped;
|
*pState = State_Stopped;
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = gst_element_get_state(This->gstfilter, &now, &pending, dwMilliSecsTimeout == INFINITE ? -1 : dwMilliSecsTimeout * 1000);
|
ret = gst_element_get_state(This->container, &now, &pending, dwMilliSecsTimeout == INFINITE ? -1 : dwMilliSecsTimeout * 1000);
|
||||||
|
|
||||||
if (ret == GST_STATE_CHANGE_ASYNC)
|
if (ret == GST_STATE_CHANGE_ASYNC)
|
||||||
hr = VFW_S_STATE_INTERMEDIATE;
|
hr = VFW_S_STATE_INTERMEDIATE;
|
||||||
|
@ -1642,6 +1724,13 @@ static ULONG WINAPI GSTOutPin_Release(IPin *iface)
|
||||||
|
|
||||||
if (!refCount) {
|
if (!refCount) {
|
||||||
if (This->their_src) {
|
if (This->their_src) {
|
||||||
|
if (This->flipfilter) {
|
||||||
|
gst_pad_unlink(This->their_src, This->flip_sink);
|
||||||
|
gst_pad_unlink(This->flip_src, This->my_sink);
|
||||||
|
gst_object_unref(This->flip_src);
|
||||||
|
gst_object_unref(This->flip_sink);
|
||||||
|
gst_object_unref(This->flipfilter);
|
||||||
|
} else
|
||||||
gst_pad_unlink(This->their_src, This->my_sink);
|
gst_pad_unlink(This->their_src, This->my_sink);
|
||||||
gst_object_unref(This->their_src);
|
gst_object_unref(This->their_src);
|
||||||
}
|
}
|
||||||
|
@ -1793,10 +1882,10 @@ static HRESULT GST_RemoveOutputPins(GSTImpl *This)
|
||||||
TRACE("(%p)\n", This);
|
TRACE("(%p)\n", This);
|
||||||
mark_wine_thread();
|
mark_wine_thread();
|
||||||
|
|
||||||
if (!This->gstfilter)
|
if (!This->container)
|
||||||
return S_OK;
|
return S_OK;
|
||||||
gst_element_set_bus(This->gstfilter, NULL);
|
gst_element_set_bus(This->gstfilter, NULL);
|
||||||
gst_element_set_state(This->gstfilter, GST_STATE_NULL);
|
gst_element_set_state(This->container, GST_STATE_NULL);
|
||||||
gst_pad_unlink(This->my_src, This->their_sink);
|
gst_pad_unlink(This->my_src, This->their_sink);
|
||||||
gst_object_unref(This->my_src);
|
gst_object_unref(This->my_src);
|
||||||
gst_object_unref(This->their_sink);
|
gst_object_unref(This->their_sink);
|
||||||
|
@ -1811,6 +1900,8 @@ static HRESULT GST_RemoveOutputPins(GSTImpl *This)
|
||||||
This->ppPins = NULL;
|
This->ppPins = NULL;
|
||||||
gst_object_unref(This->gstfilter);
|
gst_object_unref(This->gstfilter);
|
||||||
This->gstfilter = NULL;
|
This->gstfilter = NULL;
|
||||||
|
gst_object_unref(This->container);
|
||||||
|
This->container = NULL;
|
||||||
BaseFilterImpl_IncrementPinVersion((BaseFilter*)This);
|
BaseFilterImpl_IncrementPinVersion((BaseFilter*)This);
|
||||||
CoTaskMemFree(ppOldPins);
|
CoTaskMemFree(ppOldPins);
|
||||||
return S_OK;
|
return S_OK;
|
||||||
|
|
|
@ -704,8 +704,6 @@ static HRESULT WINAPI Gstreamer_YUV2RGB_SetMediaType(TransformFilter *tf, PIN_DI
|
||||||
avgtime = vih->AvgTimePerFrame;
|
avgtime = vih->AvgTimePerFrame;
|
||||||
width = vih->bmiHeader.biWidth;
|
width = vih->bmiHeader.biWidth;
|
||||||
height = vih->bmiHeader.biHeight;
|
height = vih->bmiHeader.biHeight;
|
||||||
if (vih->bmiHeader.biHeight > 0)
|
|
||||||
vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
|
|
||||||
vih->bmiHeader.biBitCount = 24;
|
vih->bmiHeader.biBitCount = 24;
|
||||||
vih->bmiHeader.biCompression = BI_RGB;
|
vih->bmiHeader.biCompression = BI_RGB;
|
||||||
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
||||||
|
@ -714,8 +712,6 @@ static HRESULT WINAPI Gstreamer_YUV2RGB_SetMediaType(TransformFilter *tf, PIN_DI
|
||||||
avgtime = vih->AvgTimePerFrame;
|
avgtime = vih->AvgTimePerFrame;
|
||||||
width = vih->bmiHeader.biWidth;
|
width = vih->bmiHeader.biWidth;
|
||||||
height = vih->bmiHeader.biHeight;
|
height = vih->bmiHeader.biHeight;
|
||||||
if (vih->bmiHeader.biHeight > 0)
|
|
||||||
vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
|
|
||||||
vih->bmiHeader.biBitCount = 24;
|
vih->bmiHeader.biBitCount = 24;
|
||||||
vih->bmiHeader.biCompression = BI_RGB;
|
vih->bmiHeader.biCompression = BI_RGB;
|
||||||
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
||||||
|
@ -810,8 +806,6 @@ static HRESULT WINAPI Gstreamer_YUV2ARGB_SetMediaType(TransformFilter *tf, PIN_D
|
||||||
avgtime = vih->AvgTimePerFrame;
|
avgtime = vih->AvgTimePerFrame;
|
||||||
width = vih->bmiHeader.biWidth;
|
width = vih->bmiHeader.biWidth;
|
||||||
height = vih->bmiHeader.biHeight;
|
height = vih->bmiHeader.biHeight;
|
||||||
if (vih->bmiHeader.biHeight > 0)
|
|
||||||
vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
|
|
||||||
vih->bmiHeader.biBitCount = 32;
|
vih->bmiHeader.biBitCount = 32;
|
||||||
vih->bmiHeader.biCompression = BI_RGB;
|
vih->bmiHeader.biCompression = BI_RGB;
|
||||||
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
||||||
|
@ -820,8 +814,6 @@ static HRESULT WINAPI Gstreamer_YUV2ARGB_SetMediaType(TransformFilter *tf, PIN_D
|
||||||
avgtime = vih->AvgTimePerFrame;
|
avgtime = vih->AvgTimePerFrame;
|
||||||
width = vih->bmiHeader.biWidth;
|
width = vih->bmiHeader.biWidth;
|
||||||
height = vih->bmiHeader.biHeight;
|
height = vih->bmiHeader.biHeight;
|
||||||
if (vih->bmiHeader.biHeight > 0)
|
|
||||||
vih->bmiHeader.biHeight = -vih->bmiHeader.biHeight;
|
|
||||||
vih->bmiHeader.biBitCount = 32;
|
vih->bmiHeader.biBitCount = 32;
|
||||||
vih->bmiHeader.biCompression = BI_RGB;
|
vih->bmiHeader.biCompression = BI_RGB;
|
||||||
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
vih->bmiHeader.biSizeImage = width * abs(height) * 3;
|
||||||
|
|
Loading…
Reference in New Issue